Ad Planner v2 · Audience Library Bridge · Creator Reach Analysis

Three connected pieces ship together. The shared theme: stop forcing the same work to be redone in three places. Audiences, ad plans, and creator-reach analysis all become reusable workspace assets instead of per-campaign one-offs.

Three connected pieces ship together. The shared theme: stop forcing the same work to be redone in three places. Audiences, ad plans, and creator-reach analysis all become reusable workspace assets instead of per-campaign one-offs.

Ad Planner v2 — top-level page + multi-campaign plans

/ad-planner was previously reachable only from inside a campaign, and each plan belonged to exactly one campaign. The new version flips it.

  • Top-level page at `/ad-planner` lists every plan in the workspace, with status, total budget, source campaign, and channel breakdown.
  • Plan detail at `/ad-planner/[id]` supports plans powering multiple campaigns. Per-campaign filter chips at the top, line-items grouped by campaign.
  • `LinkedAdPlansCard` on /campaigns/[id] shows every plan touching this campaign — no more hunting through the planner module.
  • `PlanChooserFork` on /campaigns/[id]/adplan empty state: pick an existing plan or create a new one. Removes the dead-end where new campaigns had no plan and no clear path to one.
  • Plan badges on /campaigns list rows mark which campaigns have plans attached.
  • Migration `c9e8d7f6a5b4` adds media_plans.source_campaign_id plus a backfill script that re-attaches orphan plans to campaigns by name match.

The orphan problem the migration solves: previously the planner stored items in a JSON blob on autopilot_config. Plans without items lost their campaign linkage. The new column is the durable bidirectional join.

Audience library ↔ campaign bridge

Until today, /audiences (workspace library) and the audience tab inside a campaign were unconnected surfaces. You could build a "Cart Abandoners" segment in the library, but you couldn't actually use it in a campaign without re-typing the filters.

Schema — migration d1f2e3a4b5c6 adds two thin polymorphic tables:

  • campaign_audience_inclusions — segment / custom_audience / lookalike entries that contribute to a campaign's reach. Logical-OR'd with the campaign's inline demographic filters.
  • campaign_audience_exclusions — segment / custom_audience / suppression entries that remove people from reach. Applied after the inclusion union.

Both are constrained to known kinds via CHECK, unique per (campaign, source_type, source_id), and indexed for the reverse "which campaigns use this asset" lookup.

Backend — four new endpoints under /campaigns:

  • GET /{id}/audience/library — list inclusions + exclusions with resolved asset names
  • POST /{id}/audience/library — attach an inclusion or exclusion (validates source_type per mode)
  • DELETE /{id}/audience/library/{attachment_id}?mode=include|exclude — detach
  • GET /audience-library/usage — workspace-scoped per-asset usage counts ({inclusions, exclusions, combined} keyed by {source_type}:{source_id}). Powers the badges on /audiences.

Frontend:

  • New AudienceLibraryPanel on /campaigns/[id]/audience — green chip strip for inclusions, red chip strip for exclusions, plus a "Build from library" right-side drawer with 4 tabs (Segments / Custom / Lookalikes / Suppression). Click any row to attach. Suppression tab attaches as exclusion; the rest as inclusions. Empty-state CTAs link back to /audiences.
  • "Used by N campaigns" pill on every row across all 4 tabs of /audiences. Pulls from a single usage map fetched on mount. Closes the loop the other way: heavily-used asset → see which campaigns reference it before edit/delete.

The combined effect: build "Active Customers" suppression once → attach to every acquisition campaign with one click → never waste spend on existing customers again.

Creator Reach Analysis (📊 tab on /audiences)

A 5th tab on /audiences for advertisers planning multi-influencer campaigns: how much do these creators' audiences overlap?

Pick N influencers via search, see:

  • Similarity matrix — pairwise composite score 0-100. Composite weights: niche +30, platform +20, audience age cosine 0-25, gender distribution 0-15, location 0-10, content categories 0-5.
  • Per-creator profile rail — niche, platform, audience demographics, content categories at a glance.
  • Pair-level explanation chips — "shares niche", "same platform", "62% audience-age overlap", etc. — so the score isn't a black box.

Use cases: avoid double-counting reach when picking 5 fitness influencers who share 70% audience; intentionally pick incrementally non-overlapping creators to broaden net reach.

Polish & gotchas fixed along the way

  • Bid card restriction surfacing — bids placed against creators with active brand-category restrictions now show the conflict inline on the card, not just at submit time.
  • Line-item form + CampaignWizard — Tier 1 UX cleanups: assignee picker (dropdown of workspace employees instead of UUID input), counterparty picker (searchable instead of UUID input).
  • Opportunities campaign-detail page — minor layout tweaks for budget breakdown, deliverables tab.

---

Want to see it in your own workspace?

Bring planning, discovery, delivery, and measurement into one platform.