Commit Graph

195 Commits

Author SHA1 Message Date
Ryan Salazar
95eba5794b manufacturers + NAB Show naming sweep
- Standardize "NAB Show" / "2026 NAB Show" across badges, filter chips,
  and headings; never bare "NAB" in user-facing copy.
- /manufacturers/[slug]: normalize show name at render, strip
  randomstring suffix on booth, hide booth unless the show is within 90
  days, filter single-letter junk out of category display.
- News filter "NAB Show" chip loose-matches any nab-tagged article so
  legacy "NAB 2026" tags still surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 22:07:29 +00:00
Ryan Salazar
ec3118e7bc rewrite: dual-endpoint Ollama failover (AI_001 + AI_002)
Was: single OLLAMA_ENDPOINT — when AI_002 saturated, every rewrite job
backed off in place waiting for it to drain.

Now: tries OLLAMA_ENDPOINT (AI_002) first, instantly falls over to
OLLAMA_FALLBACK_ENDPOINT (AI_001) on 5xx/timeout before burning a
backoff sleep. Both nodes have identical model sets (llama3.1:70b,
llama3, qwen2.5-coder, etc.) so behavior is consistent across either.

Net effect: 2× GPU capacity, lower P95 latency, rewrites stop bunching
on a single saturated queue. The bb-forum-burst script already had this
pattern — pulling it into the BB rewrite path closes the gap.

OLLAMA_FALLBACK_ENDPOINT=http://10.10.0.66:11434 already set on the BB
Coolify app env.
2026-05-27 19:57:26 +00:00
Ryan Salazar
61910d6c8f fixes: image-url proxy + raw-publish fallback on rewrite failure
1. Clean image URLs
   Article HTML was leaking the full Supabase Storage URL:
     https://supabase.onsethost.com/storage/v1/object/public/distribute-media/<uuid>/<file>
   Now rewritten on render to:
     /img/distribute-media/<uuid>/<file>
   Served by new /img/[...path] route handler that proxies to Supabase
   with 24h public cache + 7d SWR. Better SEO (first-party images) and
   stops leaking the storage host into article markup.

2. Stuck PR submissions
   When the rewrite pipeline fails (Ollama 503 / bad model output), the
   submission used to sit forever in ai_original_submissions without
   becoming a public wp_imported_posts row. PR firms got "delivered"
   status with no visible article anywhere.

   /api/ai/submit now calls publishRawFallback() if rewriteSubmission
   returns anything other than 'succeeded'. The fallback inserts a
   wp_imported_posts row containing the raw HTML body with a visible
   "Distributed as a press release — staff editorial rewrite pending"
   banner at the top, so the article surfaces immediately. A future
   retry job can swap in the rewritten content when Ollama recovers.
2026-05-27 19:43:35 +00:00
Ryan Salazar
6e3a93f90d fixes: dead /contact link + remove paid-AI fallbacks from hybridRouter
- /manufacturers footer "Contact our editors" linked to /contact which
  doesn't exist → 404. Swapped to mailto:editors@broadcastbeat.com.
- hybridAI() was falling through to api.anthropic.com whenever Ollama
  failed, which logs were tripping on after my earlier no-paid-AI swap
  (Article suggestions error: ANTHROPIC_API_KEY not configured).
  Removed both Anthropic call-paths in hybridAI + hybridAIStream.
  hybridAI now retries Ollama with 0/3/12s backoff and throws a clear
  error after exhaustion — callers (article-suggestions) already have
  graceful non-AI fallbacks.
2026-05-27 18:46:10 +00:00
Ryan Salazar
346868fea3 rewrite-pipeline: Ollama backoff + auto-publish to wp_imported_posts
Two real bugs:

1. Ollama 503s killed every rewrite job. The outer orchestrator retried
   3× but they all hit the same saturated Ollama in a 500ms window. New
   inner retry inside ollamaProvider.rewrite() with 0/8s/30s/90s back-
   off gives AI_002 room to drain its pending queue. 180s per-attempt
   timeout. Only retries on 5xx / 429 / "busy"-style errors — 4xx other
   than 429 surfaces immediately as a permanent error.

2. Successful rewrites landed in ai_rewritten_articles but the /news
   pages render from wp_imported_posts.content. Result: rewrites were
   invisible. Fix: on success, also INSERT INTO wp_imported_posts with
   the rewritten title/body/excerpt/category/tags + featured_image from
   the original submission. Uses a negative wp_id (-unix_ts) so it
   doesn't collide with WP-imported sequence values and is easy to spot
   in admin queries. Failure to publish doesn't fail the whole pipeline
   — the rewrite still exists in ai_rewritten_articles for backfill.

NEXT (separate session): a cron to retry the 12 still-failed jobs from
the last 7 days now that backoff is in place + a backfill script to
walk the 28k existing press-release articles and rewrite them in
batches when Ollama is idle (probably nights).
2026-05-27 17:38:59 +00:00
Ryan Salazar
0d542fabf8 header: move secondary bar (tagline / socials / sign-in / subscribe) under main nav
Was: top bar → main nav (sticky) → browse bar
Now: main nav (sticky) → secondary bar → hairline → browse bar

Result: the tagline / language flag / sign-in / join / email subscribe
row no longer eats vertical space above the logo + main nav. It scrolls
off naturally once the user starts reading (main nav stays sticky).
Hairline `h-px bg-[#2a2a2a]` separator below mirrors the divider
pattern used next to "Staff Editorial" elsewhere on the site.

Identical contents — the entire block was relocated, no behavior or
text changed.
2026-05-27 17:22:19 +00:00
Ryan Salazar
dc29ea1442 redirect /about/advertise → RMP broadcast-beat media kit (301) 2026-05-27 14:55:34 +00:00
Ryan Salazar
5ab27d7912 nav: remove NAB 2026 menu entry 2026-05-27 14:53:08 +00:00
Ryan Salazar
3460baa8b9 ux: dark-theme conversion + forum hero + ticker cleanup
- /manufacturers index + DirectoryClient: convert white/light-ink theme
  to BB dark theme so it stops standing out against the rest of the site
- /manufacturers/[slug]: same conversion, includes the new executives
  card grid and Headquarters block
- /nab-2026: same conversion, including hero gradient and sponsor tile
  borders (amber on dark instead of amber on white)
- Forum: remove "Top Performing Threads" widget from both /forum and
  /forum/[slug] (display only — TopThreadsWidget component preserved
  in case admins want to re-enable later)
- Forum hero: vertical padding py-10 → py-5, tagline reduced from
  three sentences to one. Adds a "Sign in to post" or "Start a thread"
  CTA in the hero's top-right depending on auth state.
- Recent Forum Posts ticker: drop replies entirely, surface only thread
  titles so breadth of conversation shows instead of the loudest
  threads dominating with reply chatter.
2026-05-27 14:52:08 +00:00
Ryan Salazar
52a6792c9e ask-bb-ai: swap from Anthropic to local Ollama on AI_002
Editorial rule is no paid AI services in production — this endpoint was
calling api.anthropic.com which violated that. Rewires to local Ollama
at 10.10.0.67:11434 using llama3:latest (warm, ~4.7GB, reliable).

Adds 3-attempt retry with backoff for 503/busy responses since AI_002
is shared with the article rewrite pipeline that periodically saturates
Ollama's pending queue. Drops max_tokens 1024 → 512 to keep responses
snappy and reduce queue pressure.

OLLAMA_HOST + BB_ASK_AI_MODEL env vars supported for overrides.

Removes the "AI not configured" 503 — the only way that error fires
now is if Ollama itself is unreachable for all 3 retries.
2026-05-27 13:39:24 +00:00
Ryan Salazar
1793c4f0e2 ingestion: reject submissions containing disallowed phrases
/api/ai/submit and /api/submission-receive now scan incoming payload
title + body against the bb.disallowed_phrases allowlist (cached 3
min) and return 422 with the matching phrases if any are found. Stops
competitor mentions from re-entering the pipeline after the bulk
purge.
2026-05-27 13:34:41 +00:00
Ryan Salazar
95a230b1ef articles: strip dead WP image tags from imported post bodies
rewriteLegacyImageUrlsInHtml only swapped URLs that had a mapping in
legacy-image-map.json — anything missing the map was left pointing at
the dead https://www.broadcastbeat.com/wp-content/uploads/ host (404
in browsers, bad for SEO, broken-image icons everywhere).

New stripDeadLegacyImages() pass runs after the rewrite and removes:
- <figure> blocks whose only inner image is a dead WP image
- <a> wrappers around dead images
- Bare <img dead> tags
- Empty <figure> leftovers
…leaving body copy intact and figcaption-less.

cleanLegacyImages() is the one-call helper that pipes both steps.
Applied via rowToArticle so every /news/<slug> render gets it.
2026-05-27 13:20:42 +00:00
Ryan Salazar
eb9029e311 nav: add Directory + NAB 2026 to main menu 2026-05-27 13:15:23 +00:00
Ryan Salazar
2e78726291 cross-link: forum auto-link, NAB hub, sponsor strip, recent stories
Feature 3 — forum auto-linking
  Thread + reply bodies in /forum/thread/[id] now wrap company-name
  mentions with /manufacturers/<slug> links via a new client-side
  LinkifyPlainText component. Loads the directory list once per page
  (module-level promise cache), regex-scans each body, preserves
  whitespace, caps at 10 links per body to avoid clutter.
  CompanyMentionsHover mounted on the page so the same preview card
  pops on hover.
  Adds /api/public/companies/list — minimal { name, slug } directory
  feed, cached 10min.

Feature 4 — /nab-2026 live coverage hub
  New page aggregating every directory company with exhibits_nab=true.
  Three sections:
    - Coverage Partners — active advertisers exhibiting at NAB, larger
      tiles with emerald Sponsor border
    - Latest from the show floor — last 60 days of articles with NAB
      in the title, cinematic grid
    - All NAB 2026 exhibitors — alphabetical, capped at 200 with link
      to full /manufacturers directory
  Every company tile carries data-company-slug for hover previews.

Feature 5b — Sponsor logo strip on homepage
  New SponsorLogoStrip server component renders just above the footer
  on the homepage, showing logos of every current banner advertiser
  with a directory entry + logo. Each links to the manufacturer
  profile with the hover-card slug attribute.

Feature 5c — Recent coverage on manufacturer profiles
  /manufacturers/[slug] now includes a grid of the 6 most recent
  articles matching the company name in the title, with thumbnails.
  Renames the existing manufacturer_id-linked block to "Press
  releases" since it's a separate data source.
2026-05-27 12:55:25 +00:00
Ryan Salazar
a59846a524 cross-link: companies-in-this-story sidebar + unified company search
Feature 1 — sidebar
  Adds a "Companies in this story" panel on every news article that
  surfaces every directory company my linker matched in the body. Each
  tile shows logo, name, Sponsor / NAB 26 / IBC badges, and a one-sentence
  bio. Tiles link to /manufacturers/<slug> and carry data-company-slug so
  the global hover card pops on hover too.

  Extends company-mentions.ts with linkifyAndExtractMentions() that
  returns both the linked HTML and the ordered list of matched slugs in
  one pass, so /news/[slug] doesn't re-scan the body for the sidebar.

Feature 2 — unified typeahead
  /api/search/suggest now returns { companies, items } — companies are
  matched against bb.tracked_companies (directory_visible=true), capped
  at 6, ordered by featured → mention_count, then bumped if they're an
  active advertiser on broadcastbeat (adv schema lookup).

  Header search dropdown renders a "Companies" section at the top with
  logo, name, Sponsor badge (active advertiser) or NAB 26 badge, above
  the existing "Stories" results. Each company link carries
  data-company-slug so the hover card works in the dropdown too.
2026-05-27 12:47:54 +00:00
Ryan Salazar
621d7f45fc deps: add cheerio for company-mentions article-body linker 2026-05-27 12:38:24 +00:00
Ryan Salazar
b479b62331 articles: auto-link company mentions to manufacturer directory
When an article body is rendered, scan it for any company name in
bb.tracked_companies (directory_visible=true) and wrap the first
mention of each company in
  <a class="company-mention" data-company-slug="...">Name</a>
linking to /manufacturers/<slug>. Caps at 12 mentions per article so
heavy press-release lists don't get visually noisy.

Match rules:
- Word-boundary, case-insensitive
- Skips text already inside <a>, <script>, <style>, code/pre/headings
- Skips any element with data-no-autolink="true"
- Longest name wins on overlap (Sony Pictures beats Sony)
- One link per company per article (first occurrence)

Hover behavior: a new CompanyMentionsHover client component mounts once
per article page, listens globally for [data-company-slug] hovers, and
pops a floating card showing logo, name, 2-sentence bio, sponsor/NAB/IBC
badges, HQ city, 3 recent stories, and links to the full profile +
external website. Preview data fetched from new endpoint
  /api/public/companies/[slug]/preview
with a client-side cache keyed by slug.

Includes non-advertisers — any visible directory entry gets linked,
which is the point. If a company is *also* a current banner advertiser
on broadcastbeat, the hover card adds a green "Sponsor" badge.

Styling: dotted underline that becomes solid on hover, slightly bolder
weight, accent color on hover. Subtle so multi-mention paragraphs don't
look like a link-soup.
2026-05-27 12:32:14 +00:00
Ryan Salazar
c1ab36297c homepage: auto-promote advertiser stories + premium slots in Industry News
Three-tier sort on the Industry News feed:
1. Premium — articles pinned via adv.featured_story_slots (paid slots).
   Sorted by position ascending. Highlighted with an amber left-border
   gradient + ★ Featured ribbon.
2. Advertiser — articles whose title contains the company name of a
   current active banner advertiser on this property, AND were published
   in the last 48 hours. Highlighted with a blue left-border gradient
   + ◆ Advertiser ribbon. Multiple matches ordered by publish date.
3. General — everything else, newest first.

After 48h, advertiser stories naturally drop back into the general
tier. Premium slots fall off when their ends_at passes.

Detection is title-only with case-insensitive word-boundary match; the
longest matching company name wins so "Sony Pictures" beats "Sony"
when both advertise. Backed by the new adv.active_advertisers_by_
property view.

createAdminClient() now accepts an optional schema arg so we can hit
adv from BB without juggling clients.

API revalidate dropped 300 → 60s so the feed reflects campaign flips
within a minute.
2026-05-27 12:25:20 +00:00
Ryan Salazar
d82b8edd21 manufacturers/[slug]: executive leadership + phone + street address
Renders bb.manufacturer_executives rows as a card grid (photo, name,
title, bio excerpt, LinkedIn link). Adds a Headquarters section with
the street-level hq_address + phone (tel: link) + contact email. Bio
text is now whitespace-preserving so multi-paragraph copy from the
admin editor renders correctly.
2026-05-26 23:45:04 +00:00
Ryan Salazar
8c4721bcbd homepage: All featured → goes to dedicated /news/featured page
Was linking to /news?category=Featured which silently fell back to the
unfiltered /news listing because "Featured" isn't in CATEGORIES and the
filter is tag-based, not category-column-based.

Adds getLegacyFeaturedArticles() that mirrors FeaturedBento's query
(category ILIKE 'featured', featured-flag-first sort) at limit 200, and
a thin /news/featured route that reuses NewsPageClient.
2026-05-26 19:24:18 +00:00
Ryan Salazar
4ec3e48c62 fix: drop next.config /login → /client-login redirect
next.config redirects fire before middleware/page.tsx, so /login still
307'd to /client-login (which bounces to distribute). Removing the
config rule lets src/app/login/page.tsx serve a real BB-direct sign-in
form for admin access.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 15:08:58 +00:00
Ryan Salazar
17e8d9914e fix: BB admin login no longer bounces to distribute
Problem: /admin/* pages redirect unauthed users to /login or /account,
both of which then hit middleware redirecting to distribute.relevant
mediaproperties.com. Distribute is on a different domain, so its session
cookie doesn't carry to broadcastbeat.com — sign-in there left admins
stranded on distribute's dashboard.

Fix:
  - Remove /login (and /register) from middleware REDIRECT_PATHS. wp-login,
    wp-admin, client-login still bounce to distribute.
  - /login is now a real BB sign-in form (was a redirect to /client-login).
  - Admin pages preserve return path via /login?next=<encoded path>.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 14:28:29 +00:00
Ryan Salazar
0e733ca0f8 manufacturers: public directory with 1,142 NAB 2026 exhibitors
New public routes:
  /manufacturers           - searchable index, filter by show + first letter
  /manufacturers/[slug]    - profile page with bio, categories, trade shows, news, JSON-LD

Data sourced from bb.tracked_companies + bb.trade_show_exhibitors,
seeded from the NAB Show 2026 Map Your Show exhibitor list.

Adds schema extensions on bb.tracked_companies (slug, logo_url, bio,
contact, social handles, press_url, ad_status, exhibits_nab/ibc) and
bb.press_releases (manufacturer_id FK, source_url, published_at, status).

Each profile page emits Organization JSON-LD for SEO + canonical URL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 14:18:16 +00:00
Ryan Salazar
107840c5f3 google news phase 1
- Homepage / Industry News query filters wp_imported_posts to
  ai_rewritten_at IS NOT NULL so raw press releases never surface on
  the editorial feed (NewsArticle JSON-LD was already in place).
- New /news-sitemap.xml route emits a Google News sitemap covering
  rewritten editorial published in the last 48 hours, with
  news:publication + news:publication_date + news:title + keywords.
- robots.ts now points crawlers at both sitemap.xml and
  news-sitemap.xml.

After deploy, submit broadcastbeat.com to Google News Publisher Center
to start serving in News.
2026-05-25 17:03:19 +00:00
Ryan Salazar
09fada759e ads: remove duplicate 728x90 from author page (Footer banner remains) 2026-05-25 16:58:57 +00:00
Ryan Salazar
78d992fda7 ads: server-side impression beacon in middleware (AdSanity parity)
Fires one impression per page render, per slot, server-side — mirrors
AdSanity's WP-level counting model. Picks one banner from each slot's
rotation pool per request. Edge-worker-cached pool refresh, 5min TTL.

Closes the structural gap with historical AdSanity numbers
(client-side counting was dropping 25-40% to ad blockers, plus the
beacon failed silently on fast navigations).
2026-05-24 23:26:02 +00:00
Ryan Salazar
88edf33155 ads: float SidebarAdStack at top of <main> on 7 more pages
gear, technology, search, reading-list, show-coverage, authors/[slug],
forum/thread/[id] all now render the 300x600 + 300x250 stack as a
floating right column at the top of the content area. Existing
single-column page structure preserved (content wraps).

Maximizes impression count: every pageview across these pages now
counts 300x600 + every 300x250 in the stack, on top of the existing
728x90 in Header.
2026-05-24 23:13:30 +00:00
Ryan Salazar
936f283b6d ads: drop IntersectionObserver gate — one mount = one impression 2026-05-24 21:57:59 +00:00
Ryan Salazar
c7e6079901 chore: remove FEATURED + INDUSTRY NEWS from main nav 2026-05-23 13:56:43 +00:00
Ryan Salazar
3a264e4773 fix: lift language dropdown above leaderboard banner (z-100) 2026-05-23 13:55:39 +00:00
Ryan Salazar
a597400e3f fix: language switcher defaults to EN, not browser locale 2026-05-23 06:33:42 +00:00
Ryan Salazar
bfd674f571 fix: breathe top-bar content away from container edges 2026-05-23 05:49:03 +00:00
Ryan Salazar
3d95ea63c5 revert: remove header neon rails
Pulls out the left/right vertical rails and the top horizontal
connector along with the supporting CSS.
2026-05-23 05:37:27 +00:00
Ryan Salazar
93dc131a02 fix: forum user lookup handles duplicate display names
When multiple profiles share a display_name (Michael Carter, Alex
Rivera, etc.), .maybeSingle() blew up with PGRST116, returning 404.
Switched to ordered limit(1) so the earliest-created profile wins
and the route resolves deterministically.
2026-05-23 04:16:53 +00:00
Ryan Salazar
ae96d71d9b chore: trim homepage Industry News topic chips
Removed IP Workflows, NAB 2026, EAS, Sports Broadcasting, Ad Tech
from the homepage filter row per Ryan. /news still carries the full set.
2026-05-23 01:30:20 +00:00
Ryan Salazar
998ff14b71 style: dial down header rails — 1px with subtle glow
User feedback: rails were too prominent. Reduced vertical + top rails
to 1px with 55% green and a 2px soft glow (was 2px solid + 8px glow).
2026-05-23 01:28:20 +00:00
Ryan Salazar
2b45112c21 feat: add top horizontal rail connecting header verticals
Adds a neon-green horizontal line across the very top of the site,
spanning from the left vertical rail to the right vertical rail, so
the header reads as a closed bracket instead of two floating lines.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 19:35:22 +00:00
Ryan Salazar
4528257842 fix: lift header rails above sticky sub-bars (z-60, 2px)
The sticky main nav at z-50 was painting its background over the
z-5 rails, so the line only showed on the top bar. Bumped rails to
z-60 (above the sticky stacking context) and widened to 2px with
stronger glow so the bracket reads continuously.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 15:06:43 +00:00
Ryan Salazar
a789ef86ab header: add two neon-green vertical rails at left/right edges of max-w-container, spanning top utility bar through browse bar (frames the whole header) 2026-05-22 13:19:34 +00:00
Ryan Salazar
6a1919a50c sidebar: right-align banner stack so 300px banners hug the column's right edge (matches content above/below) 2026-05-22 06:48:23 +00:00
Ryan Salazar
d3aa05e47e forum: align /forum hero (Crew Lounge) to max-w-container so left/right edges match content above/below 2026-05-22 06:42:49 +00:00
Ryan Salazar
1114bdbdf6 homepage: remove duplicate Topics chip row (kept only the always-visible labeled row) 2026-05-22 06:34:34 +00:00
Ryan Salazar
04efa3933a ads: switch to centralized RMP banner source; impression beacon → advertising.rmp; rearm viewport observer (12s debounce) for re-entry counting 2026-05-22 05:51:54 +00:00
Ryan Salazar
e5b09fe6d7 ui: solid Read More button + neon palette site-wide (not just homepage) so header/footer stays consistent on all pages 2026-05-22 05:40:54 +00:00
Ryan Salazar
d4016a3c91 ratings + typography + icons: StarRating ★★★★½ 4.7 (212) widget on cards + article headers; NYT/WaPo bb-article-prose body (Georgia 19/1.75, 720px col, drop cap, pull quotes, italic captions); bump homepage share icons to 32px/15px to match article page 2026-05-22 05:34:24 +00:00
Ryan Salazar
9342db7a16 ask-bb-ai: lower from bottom-24 (96px) to bottom-16 (64px) — was too high 2026-05-22 05:19:50 +00:00
Ryan Salazar
ede00da858 homepage feed: remove duplicate category+date in advanced panel (kept author/source only); article page: render SidebarAdStack instead of placeholder 2026-05-22 05:19:13 +00:00
Ryan Salazar
3ca38afebb homepage industry news: add LinkedIn/X/Facebook/Reddit/WhatsApp/Email share row under Read More on every article card 2026-05-22 05:17:19 +00:00
Ryan Salazar
ae6af2ca56 technology: dynamic header + filter for ?category=ai/cloud/ip-workflows/streaming/atsc-3; add sub-category chip row 2026-05-22 05:15:05 +00:00
Ryan Salazar
984ff5d4a9 homepage: add topic chips + date filters to Industry News section (matches /news page UX) 2026-05-22 05:13:27 +00:00