Commit Graph

228 Commits

Author SHA1 Message Date
Ryan Salazar
a6ab915f2d chore: empty commit to retrigger Coolify webhook for ab47136 2026-05-19 19:51:39 +00:00
Ryan Salazar
ab47136965 home(feed): rename "View Original »" link to "Read More..."
The Latest section was showing "View Original »" for imported posts and
opening every click in a new tab, even though the /api/public/posts
endpoint doesn't expose an externalUrl — every link actually went to
the internal /articles/<slug> page. Two changes:

- Link text is now "Read More..." for every article in the feed.
- target="_blank" only applies when article.externalUrl is actually
  populated (currently never, but keeps the door open for cross-site
  syndication later). Internal /articles/<slug> links now open in the
  same tab as the user expects.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:17:34 +00:00
Ryan Salazar
488a381aed home: hide dates on Featured posts site-wide; remove BB-AI trends sidebar
Dates
- FeaturedBentoFromDb hero + 4-up rail no longer render `ago(wp_published_at)`.
  The whole component renders only Featured-category posts (filter is
  category ILIKE 'featured'), so dropping the date display globally inside
  it satisfies the "Featured posts have no date" requirement.
- /gear and /technology category cards: wrap the existing `{article.date}`
  output in `{article.category !== "Featured" && ...}` so only non-Featured
  posts continue to show their publish date.
- All other category landing pages, /news/<slug> detail, ArticleFeed, and
  LiveWireTicker either don't render a visible article date or only use it
  for SEO/structured-data fields (kept).

Trends sidebar
- Remove `BB AI · detected trends` block from the homepage (between
  ArticleFeed and NewsletterSignup). Drop the unused import and delete
  the orphaned TrendsSidebar.tsx component file. The detect-trends data
  fetch and `bb_pulse_dot` mini-widget are gone from the page; nothing
  else referenced them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 23:47:00 +00:00
Ryan Salazar
308b27ec3e fix: use correct fallback image path for legacy articles 2026-05-18 22:07:30 +00:00
Ryan Salazar
fe0edf5c02 home(feed): drop LEGACY category label, vertical separator, and Imported source badge
The article-row metadata strip rendered three things side-by-side:
  {category} | <SourceBadge source>

For imported WP posts whose `category` column was the placeholder string
"legacy", this produced a meaningless "LEGACY | IMPORTED" row on the
homepage. Now:

- Hide the category text entirely when value is "legacy" (any case);
  it falls back to nothing while the AI-derived category propagates
  in a separate data pass.
- Drop the vertical pipe separator.
- Remove the yellow "IMPORTED" SourceBadge — the article's category
  is the user-relevant signal, not its data-source provenance.

The SourceBadge function is now unused and removed. The source-filter
control in the advanced filters panel is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:01:46 +00:00
83579523d1 forum: rebrand to The Crew Lounge + scaffold Credits system
UI (targeted edits, no identifier renames):
- forum/layout.tsx: metadata title/OG/twitter/schema.org + breadcrumb name → The Crew Lounge
- forum/page.tsx: H1 + intro copy → The Crew Lounge (swapped 💬 for /logos/crew-lounge-logo.svg)

New assets:
- public/logos/crew-lounge-logo.svg — film-reel + CREW logo, #5B7C8D → #8FB0C3 gradient
- src/components/crew-lounge/CreditsDisplay.tsx — per-category totals + Crew Leader badge
- src/components/crew-lounge/CrewLoungeLogo.tsx — wrapper component

Database (4 new tables on supabase01, all TIMESTAMPTZ, IF NOT EXISTS guards):
- bb.user_credits — per-user/per-category daily credit ledger
- bb.user_credits_totals — cached leaderboard totals
- bb.crew_leader_badges — awarded at 10+ credits/category
- bb.credits_monthly_snapshot — monthly leaderboard captures

Docs:
- docs/guides/FORUM_SETUP_COMPLETE.md — replication guide
- docs/guides/FORUM_SETUP_CHECKLIST.md — per-property checklist

Forum tables (bb.forum_*) and TypeScript identifiers untouched.
Routes (/forum) unchanged — display-name rebrand only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 16:08:21 +00:00
claude-code
82ae43ccf5 feat: allow cron-secret to invoke company-coverage scan with service role 2026-05-16 15:01:30 +00:00
claude-code
39311a4401 feat: wire up The Latest feed — fetch imported WP posts from new /api/public/posts 2026-05-16 12:56:16 +00:00
e4e74a9059 home: add horizontal divider line in Featured section header (matches The Latest)
Switches the Featured header row from justify-between to gap-3 + flex-wrap
so a 1px <div className="flex-1 h-px bg-[#2a2a2a]" /> can fill between the
label and the "All featured →" link. Same rhythm as The Latest header in
ArticleFeed.tsx:276-277.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 09:36:07 +00:00
24a32b9757 forum: sync metadata + schema.org with the new Industry Forum branding
Updates the forum's <head> SEO/social metadata to match the visible header.
Old "Community Forum" / "broadcast engineering professionals" copy was
leaking into Google + LinkedIn / Twitter Card previews after yesterday's
visible-text rebrand.

- metadata.title, openGraph.title, twitter.title: Community → Industry Forum
- All four description strings broadened to "broadcast, motion picture and
  post production professionals worldwide"
- Schema.org WebPage name + description aligned

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 09:31:17 +00:00
e88f7ec25e forum: rebrand header — Community → Industry, broaden audience copy
- "Community Forum" → "Industry Forum"
- "Connect with broadcast engineering professionals" → "Connect with broadcast, motion picture and post production professionals worldwide"

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 09:23:53 +00:00
1c5ae77ca4 home: remove "AI Picks For You" section from homepage
Drops <AISuggestedArticles variant="compact"> + its ScrollRevealSection
wrapper from /home-page. Component file stays — still rendered on /news
(NewsPageClient uses variant="full").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 09:21:32 +00:00
d31d9ce212 feed: intersperse mobile-only 300x250 ads every 4 articles
Mobile readers previously saw zero ads above the bottom of the feed
(header leaderboard is hidden md:block, sidebar stack pushes below the
feed at <lg breakpoints). This adds a 300x250 ad block after every 4th
article in <ArticleFeed>, wrapped in md:hidden so tablet/desktop are
untouched (they already get SidebarAdStack to the right of the feed).

- Ad pool: rotateAll('300x250') from src/lib/ads.ts (DB-backed, falls
  back to the 7-ad hardcoded pool)
- Cycles through the pool if the feed is longer than the pool
- Skips the insert after the very last article
- Labelled "Sponsored", divide-y separator inherited from parent

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 03:43:21 +00:00
migration-tooling
4c0ba55743 Use real article authors (remove Staff Reporter): removed mock staticArticles + changed fallback to Broadcast Beat 2026-05-16 00:54:53 +00:00
3722a34fab home: merge FeatureStoryHero design into FeaturedBentoFromDb
Replace the bento's old hero+side-rail layout with a full-width cinematic
21:9 hero on top + 4-up vertical-card rail below. Single DB query, single
component. Standalone FeatureStoryHero component removed (was rendered
above the bento; design now lives inside the bento itself).

- FeaturedBentoFromDb.tsx: new hero (gradient overlay, Feature Story badge,
  large overlaid title, "Read full story" cue) + 4-card grid below
- home-page/page.tsx: drop <FeatureStoryHero /> import + JSX block
- src/components/FeatureStoryHero.tsx: deleted (composition replaced by
  inline JSX in the bento for single-query efficiency)

Accent uses var(--color-text-info, #60a5fa) — the existing blue from the
codebase, matching the rest of the post-Slice-4-revert site.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 00:38:20 +00:00
broadcastbeat-rename
0db3ea34e3 Replace 'BroadcastBeat' with 'Broadcast Beat' in all user-visible text (133 occurrences across 40+ files) 2026-05-16 00:32:13 +00:00
959e3980ae Revert "slice 4: accent palette refinement — blue → teal"
This reverts commit 52fe7bceb6.
2026-05-15 23:49:36 +00:00
52fe7bceb6 slice 4: accent palette refinement — blue → teal
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>
2026-05-15 23:33:04 +00:00
f6fa7570c4 slice 3: FeatureStoryHero — single cinematic hero card above bento
New component src/components/FeatureStoryHero.tsx renders a wide 21:9 hero
of the top-pinned featured article (same query shape as FeaturedBentoFromDb).
Wired into home-page/page.tsx above the bento. Teal accent (#5B7C8D) used
intentionally — Slice 4 will sweep the rest of the site to match.

Bento remains intact and may share its top article with the hero; that's
the magazine "lead story emphasized" treatment, not a bug.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 21:58:52 +00:00
a240133ec7 slice 1+2: header social URLs, drop TwitterX/Press Kit, wrap About/Team/PressKit, add /newsletter + /rss
- Header.tsx: bare linkedin/instagram/facebook URLs → /broadcastbeat handles; remove TwitterXIcon from social bar + import (Footer retains TwitterXIcon per "DO NOT OVERRIDE" marker)
- AboutDropdown.tsx: drop "Press kit" item (the page itself stays at /about/press-kit, just delisted from menu)
- SystemStatusBar.tsx: remove "Index online" pulse-dot and "N wire sources" span (keep articles count + events count + build version)
- /about, /about/team, /about/press-kit: wrap in Header+Footer (previously rendered bare <main>)
- /newsletter/page.tsx (new): redirect to /newsletter/archive — Header was 404'ing on /newsletter
- /rss/route.ts (new): dynamic RSS feed reading from legacy-source news section; replaces 404 at /rss

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 21:49:34 +00:00
Claude (Phase B)
09a24be3de trends sidebar cleanup: minimal display + beige/navy palette
Per Ryan 2026-05-15:
- Removed Vector pass footer (no audience value)
- Removed PHASE 6b comment
- Removed corner brackets + monitoring subtitle + rank prefix
- Top 5 entities now show as Sony +12 / Riedel +8 — entity name
  serif + count mono, right-aligned
- Background #F5EFEB (beige; #F5EFFEB had 7 hex chars, interpreted as
  #F5EFEB) via new --color-trends-bg token
- Text #2F4F5B (navy) via new --color-trends-text token
- Tokens added to redesign-tokens.css palette section

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 13:53:51 +00:00
Claude (Phase B)
e791f19bd8 homepage: restore Sony bento layout (hero + side rail from Featured category)
Replaces the 5-slide FeaturedCarouselServer that lost the Sony hero +
side-rail layout. New FeaturedBentoFromDb pulls every category=Featured
row, sorts featured=true (pinned hero) first then by published date.

- Hero (left, 2 columns): Sony BRC-AM7 + HXC-FZ90 — pinned via featured=true
- Side rail (right): next 4 Featured-category posts (BVM-HX1710, MCRs/NOCs,
  Tessera SQ200, Backlight Iconik)

Old carousel kept on disk for future use. Layout matches the prior
FeaturedBento.tsx visual structure (hero + 4 small cards right).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 09:13:55 +00:00
Claude (Phase B)
4546d5be3b Phase 6C featured carousel + Phase 2 event tag chips
FeaturedCarouselServer (RSC) pulls 5 most recent featured=true rows from
bb.ai_rewritten_articles UNION bb.wp_imported_posts, renders into the
client FeaturedCarousel component: 4s auto-advance (pause on hover),
chevron + dot nav + NN/NN counter, [ID xxxx] / [CATEGORY] badges,
serif headline, lede, optional neural-summary box with corner brackets +
confidence + entity chips, byline with initials avatar. Replaces the
FeaturedBento on the homepage.

Seeded bb.wp_imported_posts.featured = TRUE on 5 recent posts so the
carousel renders out-of-the-box (Amagi, Riedel, LiveU BroadcastAsia,
Cobalt Best-of-Show, Leader MPTS).

Phase 2 UI:
  EventTagPicker (client) — chip multi-select reading bb.events,
                            writing to bb.article_events via
                            /api/admin/review-queue/[id]/events.
                            article_table=ai_rewritten for now;
                            future submission-form integration will
                            cover other tables.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 08:20:49 +00:00
Claude (Phase B)
a785ef428e BB AI redesign: 4-item nav, Show coverage, About, status bar, wire ticker,
trends sidebar, Ask BB AI, neural-summary schema, 13-event seed.

Phases 1-10 (11 phases minus Phase 8 which is the pgvector marker).

DB:
  bb.events                — 13 industry events seeded (MPTS, ANGA COM,
                             BroadcastAsia, SET Expo, IBC, NAB NY,
                             NewTECHForum, SATIS, DPP EBS, SVVS,
                             Hamburg Open, CABSAT, NAB Show '27).
  bb.article_events        — composite PK (article_id, article_table, event_id)
                             links any article-table row to one or more events.
  bb.banner_creatives      — (no change tonight)
  bb.{native_articles,
      ai_rewritten_articles,
      wp_imported_posts,
      press_releases}.neural_summary jsonb — populated by Phase 7
                             analyzer (deferred).
  bb.{native_articles,
      ai_rewritten_articles}.featured boolean — featured carousel source.

Routes:
  /api/events/upcoming     — date-gated, enriched with is_live/days_until/
                             day_of_event/total_days.
  /api/ask-bb-ai           — Claude Opus 4.7 chat with prompt caching on
                             the system block, 30-msg/hr/IP rate limit.
                             Tool-use deferred — first cut is straight
                             LLM with archive-citation prompting.

Pages:
  /show-coverage           — index of all events, upcoming + past split.
  /show-coverage/[slug]    — hero + status (live/T-Nd/past) + schema.org
                             Event JSON-LD + tagged articles list.
  /about, /about/{team,contact,advertise,press-kit}.

Components:
  Header                   — 4-item nav: Show coverage (dropdown) /
                             Newsletter / Forum / About (dropdown).
                             Old NEWS/GEAR/TECHNOLOGY/ADVERTISE items
                             removed from nav (routes still exist).
  EventsDropdown           — 340px CSI panel with L-corner brackets,
                             pulsing dot, "BB AI" badge, live/T-Nd rows,
                             auto-updated stamp.
  AboutDropdown            — 5-item lighter treatment.
  SystemStatusBar          — Index online pulse, articles/sources/events
                             counts, build version. Above Header.
  LiveWireTicker           — replaces NewsTicker. [HH:MM] [SRC] prefix,
                             CSS marquee, doubled for seamless loop.
  TrendsSidebar            — BB AI detected trends, top 5 entities by
                             7d-vs-30d lift, vector-pass stamp.
                             Phase 6b: replace with pgvector.
  AskBBAI                  — floating bottom-right button + 420px right
                             drawer chat. No mention of Anthropic/Claude.

Styling:
  redesign-tokens.css      — --color-text-info, --color-background-*,
                             --font-serif/mono/body, bb-pulse/bb-ring
                             animations + bb-marquee.

Constraints honored:
  - LiveU 728x90 still between </nav> and ticker.
  - Blackmagic 300x600 still pinned top of sidebar.
  - /r/[slug] click tracking unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 02:22:23 +00:00
Claude (Phase B)
c0584092eb ad analytics: impressions + clicks + admin stats + /r/[slug] redirect
Tables (Phase B+):
  bb.ad_impressions   — every render = a row. is_bot + viewport flags
                        for filtering. No unique constraints — gross
                        counts per Ry an spec.
  bb.ad_clicks        — every /r/[slug] hit = a row.
  bb.ad_campaign_clients — schema only; future client reporting.

FK target is bb.banner_creatives (the renderer table from 5/15 banner
refresh). bb.ad_campaigns is the AdOps billing schema and unrelated.

Routes:
  GET  /r/[slug]                   — log click, 302 to bb.banner_creatives.click_url
  POST /api/track/impression       — record impression (slug | campaign_id),
                                     used by client beacon

AdImage rewrite:
  - link href is /r/{slug} target=_blank rel=sponsored noopener noreferrer
  - sendBeacon on mount fires impression (viewport=false)
  - additional sendBeacon on IntersectionObserver ≥0.5 (viewport=true)
  - removed direct supabase.from('banner_analytics').insert path

Ad type carries slug now; FALLBACK list + DB mapper both populate it.

/admin/banners:
  per-row stats — 24h, 7d, lifetime impressions/clicks/CTR
  link to /admin/banners/[id]/analytics

/admin/banners/[id]/analytics (new):
  recharts line charts for impressions + clicks (30d), top page paths,
  top referrer domains, bot/human toggle.

Tower Products orphan image file removed; banner was not in any active
table or in code (already off rotation).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 01:45:33 +00:00
Claude (Phase B)
597eac431d ads: restore Telycam 300x250 (was dropped from FALLBACK in the 8-banner refresh)
bb.banner_creatives row also inserted (AdSanity 191740, click_url
https://telycam.com/, dates 2024-05-07 → 2026-12-31, status active). The
creative was already in public/legacy/ads/ from the pre-Phase-B banner
pool — just needed the DB row + FALLBACK entry so SidebarAdStack picks
it up before the DB cache fills.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 01:07:39 +00:00
Claude (Phase B)
280d9818ba banners: refresh 8 creatives + bb.banner_creatives + /admin/banners
Backend:
  bb.banner_creatives (new) — image_path, click_url, size, start/end_date,
  status, adsanity_source_id. Seeded with 8 banners from broadcas_temp
  WP-export DB (post_type=ads): LiveU 728x90, Blackmagic DaVinci Resolve
  20 (300x600), Studio Hero, Magewell Pro-Convert, LiveU PAYG, AJA
  ColorBox, Zixi, Lectrosonics (all 300x250). Dates and click URLs come
  from the AdSanity rows; current creatives downloaded from Wayback
  snapshot 20260316123737.

src/lib/ads.ts:
  - Reads bb.banner_creatives via anon supabase-js client, gated by
    status='active' AND start<=NOW()<=end.
  - In-process 5-min TTL cache with stale-while-revalidate. Module load
    primes the cache; clients see fallback on first hit, live DB on
    subsequent.
  - Hardcoded FALLBACK list mirrors the seeded rows so the site keeps
    rendering banners if Supabase is unreachable during deploy.
  - Public helpers (rotateAll, pickAds, ADS_728X90, ADS_300X250,
    FIXED_300X600) remain synchronous so existing client component
    callsites work unchanged.

/admin/banners:
  Admin-only list + inline editor for every banner_creatives row:
  name, click URL, start/end date, status (active|scheduled|paused|
  expired). PATCH via /api/admin/banners/[id] — service_role bypass for
  the write, user_profiles.role admin/administrator for the gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 00:49:09 +00:00
Claude (Phase B)
7d03a99215 Revert "ads: restore Magewell 300x250 banner with placeholder SVG creative"
This reverts commit bd19ef8b7f.
2026-05-14 21:23:01 +00:00
Claude (Phase B)
bd19ef8b7f ads: restore Magewell 300x250 banner with placeholder SVG creative
Magewell was removed when Adsanity went away; PENDING_ads.md flagged it
along with AJA/Zixi/Lectrosonics/Opengear/Studio Hero for real creatives.
This restores Magewell with an in-house SVG placeholder (red/dark Magewell
brand color, wordmark + tagline + CTA chip) so the sidebar 300x250 stack
gets three rotating entries again instead of two. Real Magewell creative
to land when sourced from the vendor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 21:06:49 +00:00
Claude (Phase B)
6feee16391 Phase B+: severity-aware quality gates (hard banned terms vs soft)
src/lib/ai/quality-gates.ts:
  - Read bb.banned_terms with the ban_level column so admins can mark
    a term as hard (instant fail) or soft (score nudge).
  - Hard hits join the hard-coded AI_TELLS list to instant-fail the
    rewrite (status=failed_quality, regenerate).
  - Soft hits each contribute +0.05 to the heuristic AI-detector score,
    capped at +0.30, so a borderline-slop article can be tipped over
    the 0.65 threshold without single-handedly being rejected.
  - Multi-word phrases are matched as substrings; single words use a
    boundary regex that handles hyphenated terms cleanly.
  - QualityGateResult now exposes softWordsFound alongside the existing
    bannedWordsFound; failure_reason text includes the soft list when
    the score trip is what failed.

44 terms seeded in bb.banned_terms (23 hard hype words like
"revolutionary", "industry-leading", "world-class", plus 7 PR-speak
openers like "pleased to announce"; 21 soft like "innovative",
"leverages", "robust", "transform").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 20:48:07 +00:00
Claude (Phase B)
8fd7165a6a Phase B polish: revalidatePath on approve, reject empty submissions
src/app/api/admin/review-queue/[id]/route.ts
  - Call revalidatePath on /news, /news/<slug>, /articles/<slug>, /home-page,
    section pages, and /sitemap.xml on approve/reject. The /news listing is
    ISR-cached (revalidate=1800) so approval would otherwise be invisible
    for up to 30 minutes; this flushes the cache immediately.

src/app/api/ai/submit/route.ts
  - Reject submissions where rawTitle/title is whitespace or rawContent/body
    is empty after stripping HTML. Previously empty bodies fell through into
    the LLM and Claude dutifully wrote a 400-word piece about the empty
    submission. Hard-cap at the gate to avoid the spend.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 19:38:20 +00:00
Claude (Phase B)
85599d14cc Phase B E2E: UNION rewrites into /news, accept distribute-rmp payload shape
src/lib/articles/legacy-source.ts
  - getLegacyArticleBySlug, getLegacyArticlesBySection, searchLegacyArticles,
    getLegacyRecentSlugs, getLegacyRecentSitemapEntries: each now UNIONs
    bb.ai_rewritten_articles (status=published) with bb.wp_imported_posts.
    Rewrites carry persona attribution (author/avatar/title) via a
    persona:ai_personas(slug,name,beat,avatar_url) embed.
  - Section routing for rewrites is by category, not tags (rewrites do not
    use the WP tag taxonomy — they have category strings written by Claude).

src/app/api/ai/submit/route.ts
  - Bearer auth via BB_INGEST_BEARER (preferred) or x-internal-key (legacy)
  - Polymorphic payload: accepts native {rawTitle, rawContent, ...} OR the
    distribute-rmp shape {title, body, contributor:{...}, attachments,
    submission_id, ...}. Distribute attachments + featured image survive in
    bb.ai_original_submissions.metadata.

src/app/api/ai/rewrite/route.ts
  - Same bearer auth pattern as submit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 19:28:36 +00:00
f20f4c3250 Add /api/submission-receive endpoint and wp-login/wp-admin -> distribution.rmp redirects 2026-05-14 19:22:15 +00:00
Claude Code
3bd9a716b2 Phase B: AI press-release rewrite pipeline
Pluggable rewrite client (Anthropic default, ollama/openrouter stubs)
using claude-opus-4-7 with prompt caching on system prompt + style guide.
Routes the raw submission through:
  1. bb.ai_original_submissions  — raw never edited
  2. bb.ai_rewrite_jobs          — every attempt logged with token counts
  3. quality gates               — AI-tell banned-word check + heuristic
                                   AI-detector score (threshold 0.65)
  4. regenerate up to 3x         — on banned words or detector trip
  5. bb.ai_rewritten_articles    — status ai_rewritten_pending_review
  6. /admin/review-queue          — list + detail page with approve / reject /
                                   needs-human actions; on approve, the
                                   original submission is FK-linked to the
                                   published rewrite

New files only — no existing routes or contributor flow modified.
Persona auto-selection routes by beat keywords; 6 personas seeded
(marcus-halverson, elena-vasquez, darren-okafor, sarah-quinn, mike-trayton,
priya-rao) with SVG monogram avatars in public/assets/images/personas/.

Smoke test (Matrox / ST 2110 sample press release):
- persona auto-routed to darren-okafor (protocols-specs)
- anthropic 22.3s, in=2616 / out=1372 tokens
- gates passed first try, score=0.000, no banned words
- review_queue row created

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 18:38:27 +00:00
Claude Code
8a2ca37134 remove broken auth UI from header; PR firms now authenticate at distribute platform
- Header.tsx: remove "SIGN IN" and "REGISTER" buttons from the top
  utility bar and the mobile drawer. Auth for PR firms / writers lives
  at distribution.relevantmediaproperties.com now; the local /client-login
  and /register routes are no longer the right entry point.
- src/middleware.ts (new): 301-redirect /wp-login, /wp-login.php,
  /wp-admin, /wp-admin/*, /login, /client-login, and /register to
  https://distribution.relevantmediaproperties.com/login so existing
  bookmarks and old WP-era links land somewhere useful instead of 404.
- Newsletter signup form, social icons, country selector all retained.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 17:51:24 +00:00
Claude Code
d436dc3598 P0-1 partial + AppImage fallback: clean placeholder for every broken image
P0-1 (featured-image fallback path):
- AppImage.tsx: default fallbackSrc changed from /assets/images/no_image.png
  to /assets/images/article-placeholder.svg, so any article image that
  loads but 404s gets the new clean dark gradient placeholder instead of
  the old grey BB_Gray-style box. The smoke-test article
  (matrox-video-and-amagi-collaborate-...) had a featured_image URL that
  404s on broadcastbeat.com; it now renders the new placeholder.

P0-1 batch (Wayback recovery) is NOT done — see PENDING_ads.md. The
attempted backfill via Wayback found no snapshots for the Matrox smoke
article (it is a 2026 NAB story Wayback never crawled), and the batch
loop tripped a 403 from one of its outbound calls. Proper backfill
needs per-vendor press-kit scraping and a new bb-media storage bucket.

PENDING_ads.md updated with: featured-image backfill TODOs, the banners
that still need local creatives, the hardcoded slug refactor for
NewsTicker / FeaturedBento / ArticleFeed, and the SEO re-enable gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 17:30:57 +00:00
Claude Code
02b69f2252 P0-2 + Phase A: drop Adsanity iframes, fix SEO baseline, noindex while staging
P0-2 (Adsanity iframe path removed):
- AdImage.tsx: deleted the iframe branch and the ADSANITY_CAMPAIGN_MAP
  fallback. AdImage now renders local images only; banners without a
  src render nothing.
- ads.ts: removed Ad.adsanity_id from the interface. Pruned AJA, Zixi,
  Lectrosonics, Opengear, Studio Hero, Magewell 300x250 and Blackmagic
  300x600 — they relied on ads.broadcastbeat.com which does not resolve.
  Surviving live banners: LiveU 728x90, LiveU PAYG 300x250, Telycam 300x250.
- SidebarAdStack: key no longer references the removed field.
- No banner_analytics rows reference ads.broadcastbeat.com.

Phase A (SEO baseline):
- robots.ts: fallback base URL now broadcastbeat.com (was rocket staging URL).
- sitemap.ts: fallback base URL now broadcastbeat.com (was bb-staging).
- news/[slug]/page.tsx and articles/[slug]/page.tsx: same fallback fix.
  JSON-LD now uses ISO 8601 for datePublished, absolute image URL via
  SITE_URL prefix, and publisher.logo points at /assets/images/logo.png
  instead of the 404-ing /legacy/site/broadcastbeat-logo.png.
- layout.tsx: removed the /en /es /pt /fr /de /zh /ja /ar /hi hreflang
  alternates - those routes 404 today; Phase D will reinstate. Replaced
  rocket.new Organization schema URL with broadcastbeat.com. Set
  robots index/follow=false until Phase B-D land and Rich Results passes.

Featured-image fallback hardening:
- legacy-source: rowToArticle now treats BB_Gray, no_image and placeholder
  URLs as null and falls back to the new placeholder so dead images do not
  leak through to the page.
- public/assets/images/article-placeholder.svg: new clean dark gradient
  with BroadcastBeat wordmark, NOT no_image.png style.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 16:53:42 +00:00
Claude Code
8be2ccbff2 Deploy-1 fix + P0-6/7/8: Suspense wrap, carousel arrows, banner reposition, search-box removal
Deploy-1 build failed because NewsPageClient now calls useSearchParams.
- src/app/news/page.tsx: wrap NewsPageClient in <Suspense> so static
  generation can bail out to client rendering cleanly.

P0-6 (Featured carousel arrows):
- FeaturedBento: brighten arrow buttons (text-[#ccc] over bg-[#1a1a1a]
  with #3a3a3a border; previously near-invisible #666 over the same
  background), enlarge to w-8 h-8, hover fills to blue. Wrap the
  arrow group with role/aria + Left/Right keyboard nav.

P0-7 (728x90 reposition):
- Header: move the leaderboard block from above the main nav to AFTER
  the sticky nav, so it sits between the nav and The Beat ticker.

P0-8 (remove Latest search box):
- ArticleFeed: drop the in-section search input + clear button. The
  top-right header search is now the sole search UI. searchQuery state
  remains since the active-filter chip code still references it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 16:39:24 +00:00
Claude Code
70e2aebb83 P0-3/4/5: route error boundary, null guards, slug fallback, search
P0-3 (category page crash):
- Add src/app/error.tsx and src/app/global-error.tsx so any future render
  error shows a real ref id instead of the bare client-exception toast.
- NewsPageClient: null-guard every field accessed during search/category/
  sort filtering and article render (title, excerpt, author, tags,
  category, date, image, alt). The user-visible link
  /news?category=live-production now also hydrates filter state from
  ?category= and ?search= search-params, so the dropdown actually filters.

P0-4 (/articles/[slug] 404s):
- /articles/[slug] and /news/[slug]: on slug miss, redirect("/news")
  instead of notFound(). Dead links from the hardcoded NewsTicker /
  FeaturedBento / ArticleFeed arrays now land on the news index instead
  of dead-ending on 404.
- legacy-source: add searchLegacyArticles() (title/excerpt/author ilike).

P0-5 (header search):
- Header: wire Enter on the desktop and mobile search inputs and make
  both search icons clickable buttons. Submits to /search?q=<query>.
- New /search route: server component that runs searchLegacyArticles
  and renders results in the same card style as /news.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 16:35:24 +00:00
Claude Code
243f4855a1 wip-banner-system: full banner pool, stack all 300x250s, footer 728x90
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>
2026-05-14 15:43:41 +00:00
e8c7185bf7 fix: set NODE_ENV after build so devDeps install for build 2026-05-14 13:52:38 +00:00
d2f985af28 fix: use npm install instead of npm ci (no lockfile) 2026-05-14 13:50:45 +00:00
fc8b14934b fix: simpler Dockerfile without standalone mode 2026-05-14 13:47:47 +00:00
d5259aaf47 feat: add Dockerfile to bypass nixpacks deno misdetection 2026-05-14 13:47:21 +00:00
3d225c2064 fix: force nixpacks to use npm run start, not deno 2026-05-14 13:44:27 +00:00
Claude Code
146b7259d4 wip-banner-system: real ads, logo fix, remove duplicate banner, clean footer
- Remove duplicate 728x90 LeaderboardAd below news ticker (per Ryan)
- Footer: remove NAB Official Media Partner badge
- Header: replace AdSlot stub with real AdImage rotation (3 verified 728x90s)
- Header: fix logo path from dead WP URL to local /assets/images/logo.png
- ads.ts: only include ads with verified real images (3x728x90, 2x300x250)
- ads.ts: remove 22 unrecovered ads — see PENDING_ads.md for upload list
- SidebarAdStack: handle null FIXED_300X600 gracefully (Blackmagic not recovered)
- public/legacy/ads/: 5 real ad images committed (Tower, Sony, LiveU 728x90; LiveU PAYG, Telycam 300x250)
- public/assets/images/logo.png: recovered from Wayback Machine

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 12:23:22 +00:00
Ryan
25545dac48 fix(ads): render iframe OR fallback image, not both 2026-05-11 03:22:19 +00:00
Ryan
031ff6f220 feat(ads): dual tracking - AdSanity iframe + bb.banner_analytics with fixed imports and priority prop 2026-05-11 02:54:13 +00:00
Ryan
fc003fd285 ads: add self-hosted click + impression tracking to AdImage
Migration creates bb.banner_analytics (mirrors article_analytics: public
insert, admin read), applied separately to Supabase.

AdImage becomes a client component that:
- Fires one 'impression' event per render via IntersectionObserver at 50%
  visibility (avoids counting ads scrolled past at the fold).
- Fires a 'click' event on anchor click before the new tab opens.

Renders the same <a><AppImage/></a> as before — visually unchanged. Session
ID stored in sessionStorage as bb_session_id (random uuid, lazily created).
Both parents (SidebarAdStack, ArticleBody) were already 'use client', so no
boundary shifts. Tracking is best-effort and never blocks the user.
2026-05-10 22:00:48 +00:00
Ryan Salazar
aec6d0c43d Header: also rename mobile drawer 'My Account' to 'My Profile' (parity with desktop) 2026-05-10 14:48:48 +00:00