Agency Tier 2+3 + Vendor Subcontract Polish

Cohesive client-aware UX across the agency surface. Tier 2 (audiences) and Tier 3 (campaigns / briefs / contracts / margin / restrictions) all ship together.

2nd release that day

Cohesive client-aware UX across the agency surface. Tier 2 (audiences) and Tier 3 (campaigns / briefs / contracts / margin / restrictions) all ship together.

What's now client-aware

| Entity | Tier | What ships | |---|---|---| | Marketplace bids | 1 (already) | Picker on bid create + filter + badge on /marketplace/bids | | Marketplace deals | 1 + 3 | Filter + badge on /marketplace/deals. Margin columns client_charge_amount + creator_payout_amount on marketplace_deals | | Audience segments / custom / lookalikes / suppression | 2 | Picker in all 4 modals + filter bar on /audiences + client badge on segment cards | | Campaigns | 3 | client_id column, picker in CampaignWizard basics step, filter on /campaigns | | Briefs | 3 | client_id column (UI surfacing pending) | | Contracts | 3 | client_id column (UI surfacing pending) | | Workspace reviews | 3 | client_id propagated from deal on review create | | Restrictions enforcement | 5 (semantic fix) | restrictions_guard.check_restriction_conflict() now prefers client_brand.restrictions['brand_category'] over the agency workspace's brand_category when advertiser_client_id is provided. Fixes Brand A vs Brand B cross-bleed. |

Schema (migration `9c0d1e2f3a4b`)

  • client_id (FK to client_brands ON DELETE SET NULL) added to: campaigns, briefs, contracts, workspace_reviews.
  • marketplace_deals.client_charge_amount + marketplace_deals.creator_payout_amount (Numeric(15,4), nullable). Margin = client_charge_amount - creator_payout_amount.

Backend

  • /marketplace/bids and /marketplace/deals accept ?client_id=<uuid> or ?client_id=unattributed filter.
  • /campaigns accepts the same filter.
  • All 4 audience list endpoints accept the filter (Tier 2 already wired).
  • Campaign create accepts and validates client_id via validate_client_for_agency() helper.
  • WorkspaceReview create propagates deal.client_id automatically — reviews carry the same client tag as the deal they came from.

Frontend

  • New shared component: <AgencyClientFilter> — a single dropdown with All / Unattributed / per-client. Renders only for AGENCY_ADMIN with ≥1 active client. Wired on bids, deals, campaigns. Hooks: useAgencyClients().
  • <ClientBadge> chip component for inline display.
  • CampaignWizard (basics step) gets a "Client (optional)" picker.
  • Existing audiences page already had its own picker — unchanged.

Known limits (intentional)

  • Briefs and contracts have the column but no UI picker yet — to add when those modules get the next pass.
  • Margin tracking has the columns; deal create form still uses single negotiated_price. Margin UI follows in v3 once we know if agencies want to enter both numbers at create time or capture only at completion.
  • Per-client team-member scoping, multi-tier approvals (PENDING_CLIENT_APPROVAL), and per-client favorites still deferred.

---

Want to see it in your own workspace?

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