Skip to content

feat(workflows): add the audience scene behind workflows-audience - #110305

Draft
Silthus wants to merge 25 commits into
PostHog:masterfrom
Silthus:feat/audience-scene
Draft

Silthus wants to merge 25 commits into
PostHog:masterfrom
Silthus:feat/audience-scene

Conversation

@Silthus

@Silthus Silthus commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Problem

  • People who send email from Workflows or Broadcasts have no single place for who they can reach. Opt-outs and the suppression list live in tabs on two surfaces.
  • This is slice 1 of the Audience MVP: the /audience scene that later slices fill with recipients, engagement and setup.
  • Everything sits behind the workflows-audience flag. With the flag off, nothing a user sees changes.

Changes

  • With workflows-audience on, a new Audience item under Messaging opens /audience with two tabs: Topics (today's opt-out content, unchanged) and Suppression list.
  • With the flag on, opening the Opt-outs or Suppression list tab of Workflows or Broadcasts, by click or bookmark, replaces the URL with /audience/topics or /audience/suppression. Back returns to the page before.
  • The tabs stay in the Workflows and Broadcasts tab bars. With the flag off, they work exactly as today.
  • A late flag load still redirects: the redirect logic also listens for the flags arriving.
  • /audience with the flag off renders the not-found page, and the sidebar item is hidden.
  • The Audience header shows one breadcrumb per tab, like Workflows, so it has no back arrow to the page it is already on.
  • The Audience item has no intents while it is flagged. Onboarding and the backend pick a product by intent without checking flags, so Audience would otherwise replace Broadcasts there.
  • New event: messaging tab redirected to audience (tab, from).
  • Adjacent features now emit events too, with the flag on or off. Each fires only after its request succeeds:
Feature Event
Opt-out CSV export and import messaging opt-outs exported, messaging opt-outs imported (count, only when someone was opted out)
Suppression list messaging suppression added, messaging suppression removed
Topics messaging topic created, messaging topic updated, messaging topic deleted
Preview opt-out page messaging preferences page opened (only when the browser opens the tab)
Customer.io import messaging customer.io import completed (source: api or csv)
  • products/workflows/CONTEXT.md defines recipient, topic, topic status, suppressed, unreachable person and engagement events, and separates Audience from batch audience.
  • Mechanical: Broadcasts tab parsing moves from the component into broadcastsSceneLogic, NewCategoryButton moves to its own file, the opt-out header actions wrap in a narrow scene, Audience joins the sidebar products without a docs page, and the generated product files are regenerated.

Running dev stack, flag on (invented seed data). These shots predate the one-breadcrumb change, so the header may still show a back arrow that is now gone. The integration stack proof replaces them.

/workflows/opt-outs lands on Topics /broadcasts/suppression lands on Suppression list
Workflows opt-outs redirected Broadcasts suppression redirected

Storybook, full width and the 520px narrow variant:

Topics Suppression list
Topics Suppression list
Topics narrow Suppression list narrow

How did you test this code?

Test rationale: the closest existing test, Broadcasts/broadcastsRoutes.test.ts, only resolves routes to scenes, so it cannot see a redirect, a flag gate or an event.

  • Audience/audienceRouting.test.ts mounts only the Workflows or Broadcasts scene logic it opens, with the real router and flag logic. It catches a moved tab that does not redirect with the flag on (by bookmark or click), redirects with the flag off, shows the wrong tab with the flag off, pushes instead of replacing, tracks twice, or misses a late flag. It also checks the tab and breadcrumb each /audience URL shows.
  • Audience/AudienceScene.test.tsx catches the scene rendering with the flag off.
  • OptOuts/optOutListLogic.test.ts, OptOuts/topicsUsageTracking.test.ts and Suppression/suppressionListLogic.test.ts catch a dropped, doubled or misplaced tracking event, a wrong import count, and an event for a blocked popup.
  • sidebarProductMeta.test.ts failed for Audience before this PR listed it among products without docs.
  • Mutation checks: dropping the redirect connection from Broadcasts, the flag guard from the scene, the late-flags listener, or the flag check from the Workflows early return each fails its cases.
  • Local gate, because fork CI waits for maintainer approval: hogli ci:preflight --strict with 0 failures, the diff-aware devex and security semgrep with 0 findings, and typescript:check with no errors in the diff.
  • Not checked: a flag-off render on a running stack, and the stories after the last changes (no Storybook run after the one-breadcrumb and width-class commits).

Verify:

hogli test products/workflows/frontend/Audience/audienceRouting.test.ts products/workflows/frontend/Audience/AudienceScene.test.tsx products/workflows/frontend/OptOuts/topicsUsageTracking.test.ts products/workflows/frontend/OptOuts/optOutListLogic.test.ts products/workflows/frontend/Suppression/suppressionListLogic.test.ts products/workflows/frontend/Broadcasts/broadcastsRoutes.test.ts frontend/src/layout/panel-layout/sidebarProductMeta.test.ts

Expect Test Suites: 7 passed, 7 total and Tests: 103 passed, 103 total (output on 002c70e1e4a).

👉 Stay up-to-date with PostHog coding conventions for a smoother review.

Release status

  • No feature flag controls this change
  • This change is behind a feature flag and is not available to users
  • This change makes a previously flagged feature available to everyone

The adjacent tracking events fire with the flag off too. Nothing a user sees changes with the flag off.

Automatic notifications

  • Publish to changelog?

Docs update

None. The glossary lands in products/workflows/CONTEXT.md.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Agent: Claude Code, claude-opus-5-5

  • Slice 1 of the Audience MVP spec (Silthus/posthog#186), ticket Silthus/posthog#187.
  • Skills: /tdd, /writing-tests, /writing-kea-logics, /writing-ui-components, /writing-user-facing-copy, /writing-code-comments, /domain-modeling, /reviewing-with-coderabbit, /writing-pr-descriptions, /code-review, /writing-voice.
  • CodeRabbit CLI: skipped, signed out with no person available.
  • No duplicate: an open-PR search for Audience found only batch audience work.
  • Adversarial review, Fable 5.1: fixed the double redirect and double event on a Workflows tab click, and the import event firing with nothing imported. Accepted: a not-found flash before flags arrive (the app holds its first render until flags load), * in recipient URLs (deferred to the recipient detail slice), alphabetical sidebar order, the old "category" words (the Topics slice renames them).
  • Adversarial review, GPT-6.1 Sol: the Codex lane died on reconnect errors with no findings. A fresh Opus 5.5 reviewer replaced it, as the map asks. Fixed: the flagged item leaking into onboarding and backend intent lookups, and a wrong route-order comment. Accepted: dropped search and hash on redirect (the moved tabs take none), one extra Workflows pageview on a bookmark, a bogus tab URL kept while showing Topics.

QA swarm

  • Restacked once onto master at the start (old head f406ad03a44). Every fix after that is a new commit.
  • Four rounds. Each ran six GPT-6.1 Sol lenses (Codex companion) and two Fable 5.1 lenses. Round 4 came back clean on 002c70e1e4a.
Source Fixed Rejected Follow-up
GPT-6.1 Sol panel 7 3 0
Fable 5.1 panel 5 10 4
Review bots 0 0 0
  • Fixed: the failing sidebar docs test, the routing test isolation and coverage gaps, a self-linking back arrow in the header, an inline style in the stories, missing Topics tab tracking, and an event that fired for a blocked popup.
  • Gaps: fork CI waits for maintainer approval. CodeRabbit skipped every review after the restack ("couldn't safely recover the incremental review"), and Greptile never reviewed. The running-app proof happens later on the integration stack.

🤖 Generated with Claude Code

@trunk-io

trunk-io Bot commented Oct 1, 2026

Copy link
Copy Markdown

Merging to master in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: PostHog/posthog/.coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: fa6da2e1-ed9b-4434-884a-7cd8d7705337

📥 Commits

Reviewing files that changed from the base of the PR and between 6e7cc9f and f406ad0.

📒 Files selected for processing (22)
  • frontend/src/lib/constants.tsx
  • frontend/src/productScenes.tsx
  • frontend/src/products.json
  • frontend/src/products.tsx
  • frontend/src/scenes/sceneTypes.ts
  • products/workflows/CONTEXT.md
  • products/workflows/frontend/Audience/AudienceScene.stories.tsx
  • products/workflows/frontend/Audience/AudienceScene.tsx
  • products/workflows/frontend/Audience/audienceRouting.test.ts
  • products/workflows/frontend/Audience/audienceSceneLogic.ts
  • products/workflows/frontend/Audience/movedMessagingTabsLogic.ts
  • products/workflows/frontend/Broadcasts/BroadcastsScene.tsx
  • products/workflows/frontend/Broadcasts/broadcastsSceneLogic.ts
  • products/workflows/frontend/MessagingTabActions.tsx
  • products/workflows/frontend/OptOuts/NewCategoryButton.tsx
  • products/workflows/frontend/OptOuts/OptOutScene.tsx
  • products/workflows/frontend/OptOuts/optOutListLogic.test.ts
  • products/workflows/frontend/OptOuts/optOutListLogic.ts
  • products/workflows/frontend/Suppression/suppressionListLogic.test.ts
  • products/workflows/frontend/Suppression/suppressionListLogic.ts
  • products/workflows/frontend/WorkflowsScene.tsx
  • products/workflows/manifest.tsx

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 8 remain after this review.


📝 Walkthrough

Walkthrough

The change adds a feature-flagged Audience scene with Topics and Suppression tabs, routes, and tab state. It redirects selected Workflows and Broadcasts URLs to Audience when the flag is enabled. It also adds analytics capture for opt-out imports and exports and suppression additions and removals, with tests and Storybook stories.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to f406a

The flagged Audience scene and messaging-tab redirects appear mergeable after normal checks. No concrete current-head issue remains that would block merging.

Security Architecture Review

Security architecture risk: 🔵 Low · up to f406a

The new page reuses existing audience-management controls rather than granting new privileges. No introduced security issue was demonstrated, but server-side authorization and live rollback behavior were not fully verified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated exposure is an additional frontend entrypoint to existing project-scoped consent and suppression operations. Audience reuses their existing owners, and the inspected mutation calls still pass the current project or team identifier. No expansion to another tenant or privileged backend service was demonstrated; server-side enforcement remains unverified.

Trust Boundaries and Controls

  • observed — User-controlled route values select fixed tab destinations rather than arbitrary redirect targets. Invalid Audience tabs fall back to Topics. The feature flag gates rendering and redirection, while Audience retains the legacy scenes' product key and Workflow resource mapping; the flag itself is not a substitute for API authorization.

Resilience and Maintainability Implications

  • observed — The available merge-base comparison shows that the inspected opt-out import and suppression mutation changes only add analytics imports and captures. Existing partial-import handling preserves acknowledged chunks and reports the remainder; no new rollback of consent data is introduced by the redirect logic. Backend idempotency and interruption across scene unmounts were not verified.
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description is complete and self-contained. It explains the problem, user-visible changes, feature-flag behavior, testing rationale and evidence, release status, documentation status, agent contex…
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Comment @coderabbitai help to get the list of available commands.

Silthus and others added 17 commits October 2, 2026 15:07
…o audience (red)

Adds the workflows-audience flag and moves the Broadcasts tab parsing
into broadcastsSceneLogic so the routing test can mount both scene logics.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nd workflows-audience

Declares the Audience scene, its routes and url helpers, and a flagged
Messaging sidebar item. With the flag on, the Opt-outs and Suppression list
tab URLs of Workflows and Broadcasts are replaced by their Audience tab.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ist tabs

The Topics tab renders today's opt-out content and the Suppression list tab
renders SuppressionScene. NewCategoryButton moves to its own file so both
the shared messaging tabs and Audience can use it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…opened (red)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…opened

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…changes (red)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…test (red)

jsdom cannot stream a File through Papa, so the first red failed on file
reading rather than on the missing event.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…changes

Fires messaging opt-outs exported, messaging opt-outs imported (count),
messaging suppression added and messaging suppression removed, with the
workflows-audience flag on or off.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ypes

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Workflows re-pushes the moved tab after the redirect, which redirects and
tracks a second time.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
workflowsSceneLogic no longer selects a tab that Audience takes over, so its
tab url sync cannot push the moved url back after the redirect.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ut (red)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Onboarding and the backend pick a product item by intent without checking
its flag, so Audience replaced Broadcasts as the workflows product there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Silthus
Silthus force-pushed the feat/audience-scene branch from f406ad0 to b4453c3 Compare October 2, 2026 15:10
Silthus and others added 8 commits October 2, 2026 15:31
sidebarProductMeta.test.ts failed for the new Audience item: it has no
docsHref and was missing from SIDEBAR_PRODUCTS_WITHOUT_DOCS. Audience has
no docs page yet, the same as Broadcasts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… and click

Each case mounts only the Workflows or Broadcasts scene logic it opens, so
a surface that stops connecting the redirect logic fails its own cases.
Every moved tab is opened both before the scene mounts (bookmark) and
after (click).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…f link

Two crumbs plus the project crumb made the scene header show a back
arrow to /audience, the page the user is on. Audience now returns one
crumb per tab, like Workflows.

The routing test also clears persisted flags before each case, checks
the late-flags redirect fires once with REPLACE, and checks each surface
keeps its tab with the flag off.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…io imports (red)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…o imports

The Topics tab now lives in the Audience scene, so its own actions are
adjacent to this change and get usage tracking too. New events:
messaging topic created, messaging topic updated, messaging topic
deleted, messaging preferences page opened, and messaging customer.io
import completed (source api or csv). Each fires after the request
succeeds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The link is generated before window.open, so a browser can block the
popup. window.open then returns null and no page opens, and the event
no longer fires.

The opt-out list test spies on URL.createObjectURL instead of replacing
it, and the tracking test fixtures drop their type casts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant