ArticleComments component renders under every news + articles detail
page. Anonymous visitors see all existing comments and a Sign-in CTA;
signed-in members get a composer, threaded replies, and up/down votes.
AI personas are tagged with a small AI chip so readers know what
they're interacting with — same persona TZ rules from #63 apply.
Schema extensions on bb.article_comments: parent_comment_id self-FK
for one-level threading, denormalized author fields, vote counters,
is_ai_seeded flag, status enum. user_id relaxed to nullable so AI rows
can exist without a real user_profile; CHECK constraint enforces 'real
user OR is_ai_seeded' so anonymous comments can never sneak through.
forum_votes.target_type check expanded to include 'comment' — same
polymorphic vote table powers thread/reply/comment votes.
New Related Forum Posts sidebar on both /articles/[slug] and
/news/[slug]. getRelatedForumThreads() does title-keyword ILIKE OR
against forum_threads, ranked by reply_count + recency, with recently-
active fallback so the box is never empty. 6 threads per article.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
- Replace img.rocket.new mock URLs in authors/[slug] + FeaturedBento
with /assets/images/article-placeholder.svg (live DB data drives
these pages now; the static maps are fallback only).
- Replace broadcastb5322.builtwithrocket.new with broadcastbeat.com
in admin API routes, news detail, forum layout, and the 4 Supabase
edge functions that build outbound email links.
- Swap rocket-hosted JSON-LD logo URL in root layout for the local
/assets/images/logo.png we already ship.
- Drop img.rocket.new from the Next.js image-hosts allowlist;
add supabase.onsethost.com so storage proxy URLs can render.
The DB has had zero rocket.new image refs for weeks; this finishes
the job in the codebase so no page on broadcastbeat.com can render
a rocket-hosted asset anymore.
Replace the four accent-family hex codes site-wide with the teal palette
(keeps the dark theme; only swaps the accent family, not the dark base):
#3b82f6 (accent primary CTA) → #5B7C8D (teal) [839×]
#2563eb (accent-dark / hover) → #4A6473 (darker teal) [44×]
#60a5fa (info link, lighter) → #8FB0C3 (mid teal) [68×]
#1e3a5f (accent-muted bg) → #2F4F5F (navy) [44×]
tailwind.config.js tokens updated to match (accent/accent-dark/accent-muted).
Sweep also covers tailwind.css. 108 files touched. The dark base (#0d0d0d,
#111, #161616, #1a1a1a etc.) is intentionally untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Banner spec from Ryan:
- 728x90 header (every page): pool is now LiveU only; rotates when more
728x90 entries are added to ads.ts.
- 728x90 below main menu on article pages: removed (was the article-top
AdSlot stub).
- 728x90 footer (every page): replaces the old 300x250 rocket.new
placeholder in Footer.tsx. Rotates from the same ADS_728X90 pool.
- 300x250 sidebar: now renders EVERY entry in ADS_300X250 stacked
vertically, shuffled per page-view. Pool: AJA, Zixi, Telycam, LiveU,
Lectrosonics, Opengear, Studio Hero, Magewell. Adding more entries to
ads.ts auto-grows the stack.
- 300x600 fixed slot: Blackmagic (Adsanity ad-210571), top of sidebar,
never rotated.
Implementation:
- ads.ts: Ad interface now allows either src (local image) or adsanity_id
(iframe). New rotateAll() returns ALL ads of a size shuffled; pickAds
kept for ArticleBody bounded slots.
- AdImage.tsx: dropped the legacy ADSANITY_CAMPAIGN_MAP label fallback —
ads.ts is now the single source of truth, so Telycam (no adsanity_id)
renders its local GIF instead of the iframe.
- SidebarAdStack.tsx: rotateAll(300x250) instead of pickAds with a
count cap. count prop removed; callers updated.
- NewsArticleDetailClient.tsx: removed article-top 728x90 stub.
- Footer.tsx: 728x90 AdImage in place of the 300x250 placeholder.
- LeaderboardAd.tsx: deleted (was unused after the earlier home-page
cleanup).
- PENDING_ads.md: rewritten — Adsanity IDs to verify (Studio Hero,
Blackmagic) and optional local-file upgrades.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the placeholder Unsplash banner array with the live AdSanity
inventory served from /legacy/ads/ (same 27 active creatives currently
on broadcastbeat.com).
New:
- src/lib/ads.ts: ad inventory (1 fixed Blackmagic 300x600, 22 rotating
300x250s, 4 leaderboard 728x90s) + Fisher-Yates shuffle + pickAds()
helper that draws N unique ads excluding any caller-supplied src set.
- src/components/AdImage.tsx: renders one ad as <a target=_blank rel=
sponsored noopener noreferrer><AppImage>.
- src/components/SidebarAdStack.tsx: Blackmagic 300x600 fixed on top,
then count rotating 300x250s. Memoizes the rotation per render so a
refresh re-shuffles. Accepts excludeSrcs to coordinate with other
slots on the same page.
- src/components/ArticleBody.tsx: splits article HTML at </p>
boundaries, splices a 300x250 ad after every Nth paragraph (default
4). Same exclude-set pattern.
Wiring:
- ArticleFeed.tsx: remove the inline sidebarAds array (was 5 Unsplash
placeholders + 1 LiveU 300x250). Sidebar now <SidebarAdStack count=5/>.
- NewsArticleDetailClient.tsx: sidebar 300x600 + 300x250 AdSlot
placeholders replaced with <SidebarAdStack count=4/>. Article body
innerHTML replaced with <ArticleBody html=... everyN=4/> so
300x250 ads slot in between paragraphs.
Same banners as the live broadcastbeat.com (sourced from the existing
WP AdSanity install we cached locally during Phase 2). Each <a> opens
in a new tab and uses rel='sponsored noopener noreferrer'.
Eliminates 72 hardcoded rocket.new image refs in one shot. Section pages
(/gear, /technology, /show-coverage, /news) now render from
bb.wp_imported_posts via getLegacyArticlesBySection(section), which maps
each non-news section to a curated tag set (e.g. gear -> Audio, Cameras,
lighting, Blackmagic Design, monitoring; technology -> AI, Streaming,
OTT, IP, Cloud, AV; show-coverage -> NAB, IBC, BroadcastAsia).
Also:
- src/lib/articles/types.ts: Article interface lifted out of the deleted
seed file. Type-only consumers (NewsArticleDetailClient,
ArticleDetailClient, legacy-source) re-imported from here.
- /news (client component) now wrapped: server fetches articles from DB
and passes to NewsPageClient. Filter UI unchanged.
- generateStaticParams on /news/[slug] and /articles/[slug] no longer
references seed slugs; pre-renders 50 most-recent imported slugs and
relies on dynamicParams + revalidate=3600 for the rest.
- sitemap.ts now sources article URLs from
getLegacyRecentSitemapEntries() (up to 5000 most-recent imported posts).
Default BASE_URL fallback updated from the rocket.new placeholder to
bb-staging.onsethost.com.
- src/app/api/ai/article-suggestions/route.ts now pulls candidates from
the DB (top 100 recent news) and resolves AI-returned slugs via DB
lookup if not in the candidate window.
Inline rocket.new image refs in homepage components (ArticleFeed,
FeaturedBento) are unchanged in this commit; those are inline seed
arrays in the components, not imports of sampleArticles.
Adds <AdSlot zone size /> server component that renders a labeled
placeholder for now (size + zone exposed as data attrs for the future
bb.ad_placements lookup).
Inserted on news/[slug] article template:
- 728×90 leaderboard above the <article>
- 300×600 half-page at top of sidebar
- 300×250 mid-rectangle in sidebar (replaces the old hand-rolled
placeholder div)
- 728×90 leaderboard below the </article>, before related-articles
Same scheme should be applied to articles/[slug]/page.tsx in a follow-up
once that template's body is finalized.
Drops the rendered date span from:
- ArticleFeed.tsx (homepage feed)
- news/page.tsx (news list — also drops the orphan separator)
- news/[slug]/NewsArticleDetailClient.tsx (article header + related-articles row, drops paired separators)
- AISuggestedArticles.tsx (sidebar — keeps readTime alone)
JSON-LD datePublished/publishedTime in news/[slug]/page.tsx and
articles/[slug]/page.tsx is unchanged so search engines still see the
publication date.