Skip to content

feat(web-analytics): list, refresh and dismiss content opportunities - #108593

Open
jordanm-posthog wants to merge 4 commits into
jordanm-posthog/aeo-cg-03-opportunities-backendfrom
jordanm-posthog/aeo-cg-04-opportunities-api
Open

jordanm-posthog wants to merge 4 commits into
jordanm-posthog/aeo-cg-03-opportunities-backendfrom
jordanm-posthog/aeo-cg-04-opportunities-api

Conversation

@jordanm-posthog

@jordanm-posthog jordanm-posthog commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Content autopilot can rank questions that AI assistants answer without citing the site (#108121), but nothing outside the backend can read that list yet. The workspace UI in a later PR needs an API to list, refresh and dismiss those opportunities.

Part 4 of the content autopilot stack, stacked on #108121, behind the web-analytics-content-autopilot flag.

Changes

  • A new project endpoint, web_analytics_content_autopilot_opportunities, lists a site's opportunities, highest priority first. profile_id is required.
  • refresh re-reads the AEO citation checks for one site and returns the updated list. It makes no model calls.
  • dismiss hides one opportunity. It returns 404 for another team's opportunity, and refuses one that a run is drafting.
  • Each opportunity carries its citation gap as typed fields: engines, competitor pages, cited site pages and the latest answer per engine.
  • The proposal list now also defers the large brief, source_ledger and research columns.
  • The pagination guard lists the new viewset as a reviewed exception, like the other content autopilot viewsets. Its list is the standard DRF one, wrapped by extend_schema_view to document profile_id.
  • Mechanical: regenerated frontend types and the MCP API client. No MCP tool uses the endpoints yet.

How did you test this code?

  • The API test for opportunities covers the required profile_id, listing per site, the typed gap payload and dismissal.
  • The cross-team test now also checks that dismissing another team's opportunity returns 404.
  • The lifecycle test now checks that the opportunity model is team-scoped.
  • Not checked: the endpoints against a running stack. The UI that calls them lands in a later PR.

👉 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

Automatic notifications

  • Publish to changelog?

Docs update

None.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Agent: Claude Code, Claude Opus 5.5 (1M context)

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@jordanm-posthog jordanm-posthog self-assigned this Sep 29, 2026
@jordanm-posthog
jordanm-posthog added this pull request to stack #108594 September 29, 2026 18:03
@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

🤖 CI report

⚠️ Trunk lane — backend Python lane

This PR is assigned to the backend Python lane. It runs backend Python tests and may merge in parallel with PRs in other lanes.

✅ Complexity (TypeScript) — clean

Cyclomatic complexity above the limit in changed typescript files (10 for production files, 15 for test files). Warn only: worth simplifying when you next touch these functions.

⚠️ Duplication (Python) — 61 new duplicated blocks (worst 208 tokens)

New Python code duplication introduced by this branch. Fails at 70+ tokens in app code, or 150+ tokens when both copies live in test files. Advisory while the gate proves itself: extract a shared helper instead of copying.

First copy Second copy Lines Tokens
posthog/temporal/experiments/workflows.py:187 posthog/temporal/experiments/workflows.py:282 45 208
posthog/temporal/ai_observability/run_aggregate_evaluation.py:692 posthog/temporal/ai_observability/run_trace_evaluation.py:928 38 203
products/warehouse_sources/backend/temporal/data_imports/tests/e2e/test_import_data.py:133 products/warehouse_sources/backend/temporal/data_imports/tests/e2e/test_import_data.py:259 50 189
products/warehouse_sources/backend/temporal/data_imports/sources/fly_io/tests/test_fly_io.py:25 products/warehouse_sources/backend/temporal/data_imports/sources/moxie/tests/test_moxie.py:28 21 179
products/warehouse_sources/backend/tests/management/test_backfill_warehouse_queue_state.py:40 products/warehouse_sources_queue/backend/core/jobs_db.py:945 13 175
products/warehouse_sources/backend/temporal/data_imports/sources/companycam/source.py:1 products/warehouse_sources/backend/temporal/data_imports/sources/motion/source.py:1 21 164
products/warehouse_sources/backend/temporal/data_imports/sources/companycam/source.py:1 products/warehouse_sources/backend/temporal/data_imports/sources/wix/source.py:1 21 164
products/warehouse_sources/backend/temporal/data_imports/sources/companycam/source.py:1 products/warehouse_sources/backend/temporal/data_imports/sources/expo/source.py:1 21 162
products/warehouse_sources/backend/temporal/data_imports/tests/e2e/test_import_data.py:144 products/warehouse_sources/backend/temporal/data_imports/tests/e2e/test_import_data.py:354 38 156
products/warehouse_sources/backend/temporal/data_imports/sources/descope/descope.py:4 products/warehouse_sources/backend/temporal/data_imports/sources/motion/motion.py:1 20 145
products/warehouse_sources/backend/temporal/data_imports/sources/adroll/source.py:15 products/warehouse_sources/backend/temporal/data_imports/sources/cloudinary/source.py:17 16 144
products/warehouse_sources/backend/temporal/data_imports/sources/front/front.py:3 products/warehouse_sources/backend/temporal/data_imports/sources/spot_io/spot_io.py:3 20 141
products/workflows/backend/presentation/views/hog_flow.py:5339 products/workflows/backend/presentation/views/hog_flow.py:5438 25 139
posthog/temporal/ai_observability/run_aggregate_evaluation.py:584 posthog/temporal/ai_observability/run_trace_evaluation.py:855 24 135
products/warehouse_sources/backend/temporal/data_imports/sources/convertkit/convertkit.py:1 products/warehouse_sources/backend/temporal/data_imports/sources/fulcrum/fulcrum.py:2 18 134
products/signals/backend/scout_harness/tools/report.py:1475 products/signals/backend/scout_harness/tools/report.py:1647 21 133
products/mcp_analytics/backend/hogql_queries/model_breakdown.py:48 products/mcp_analytics/backend/hogql_queries/protocol_version_breakdown.py:50 14 121
products/signals/backend/scout_harness/tools/report.py:1604 products/signals/backend/scout_harness/tools/report.py:1754 30 118
posthog/temporal/ai_observability/run_session_evaluation.py:466 posthog/temporal/ai_observability/run_trace_evaluation.py:664 24 115
products/warehouse_sources/backend/temporal/data_imports/sources/dovetail/dovetail.py:4 products/warehouse_sources/backend/temporal/data_imports/sources/front/front.py:4 17 114
products/batch_exports/backend/api/batch_export.py:2092 products/batch_exports/backend/api/batch_export.py:2131 24 112
products/warehouse_sources_queue/backend/core/jobs_db.py:203 products/warehouse_sources_queue/backend/core/jobs_db.py:264 11 112
products/workflows/backend/management/commands/backfill_hogflow_billable_action_types.py:49 products/workflows/backend/management/commands/refresh_hog_flows.py:69 23 112
products/warehouse_sources/backend/temporal/data_imports/sources/capsule_crm/capsule_crm.py:138 products/warehouse_sources/backend/temporal/data_imports/sources/freshservice/freshservice.py:119 23 105
products/warehouse_sources/backend/temporal/data_imports/sources/aha/aha.py:227 products/warehouse_sources/backend/temporal/data_imports/sources/freshservice/freshservice.py:164 15 104
products/warehouse_sources/backend/temporal/data_imports/sources/aha/aha.py:227 products/warehouse_sources/backend/temporal/data_imports/sources/fulcrum/fulcrum.py:182 15 104
products/workflows/backend/presentation/views/hog_flow.py:7080 products/workflows/backend/presentation/views/hog_flow.py:7124 16 104
products/warehouse_sources/backend/temporal/data_imports/sources/ashby/ashby.py:12 products/warehouse_sources/backend/temporal/data_imports/sources/cloudinary/cloudinary.py:5 12 102
products/warehouse_sources/backend/temporal/data_imports/sources/common/byte_bounded_extraction_flag.py:47 products/warehouse_sources/backend/temporal/data_imports/sources/common/keyset_full_load_flag.py:50 15 99
products/warehouse_sources/backend/temporal/data_imports/sources/freshcaller/freshcaller.py:187 products/warehouse_sources/backend/temporal/data_imports/sources/freshdesk/freshdesk.py:282 18 98
products/warehouse_sources/backend/temporal/data_imports/sources/aha/aha.py:229 products/warehouse_sources/backend/temporal/data_imports/sources/front/front.py:182 13 97
products/warehouse_sources/backend/temporal/data_imports/sources/pardot/pardot.py:2 products/warehouse_sources/backend/temporal/data_imports/sources/wix/wix.py:1 12 97
products/mcp_analytics/backend/hogql_queries/dashboard_series.py:16 products/mcp_analytics/backend/hogql_queries/protocol_version_breakdown.py:8 20 96
products/warehouse_sources/backend/temporal/data_imports/sources/brex/brex.py:20 products/warehouse_sources/backend/temporal/data_imports/sources/freshdesk/freshdesk.py:19 25 96
products/warehouse_sources/backend/temporal/data_imports/sources/cloudinary/cloudinary.py:64 products/warehouse_sources/backend/temporal/data_imports/sources/motion/motion.py:57 15 96
products/warehouse_sources/backend/temporal/data_imports/sources/flexmail/flexmail.py:28 products/warehouse_sources/backend/temporal/data_imports/sources/freshservice/freshservice.py:5 13 96
products/warehouse_sources/backend/temporal/data_imports/sources/bamboohr/bamboohr.py:35 products/warehouse_sources/backend/temporal/data_imports/sources/fulcrum/fulcrum.py:18 21 92
products/warehouse_sources/backend/temporal/data_imports/sources/cloudability/cloudability.py:13 products/warehouse_sources/backend/temporal/data_imports/sources/cloudinary/cloudinary.py:5 11 92
products/workflows/backend/presentation/views/message_assets.py:243 products/workflows/backend/presentation/views/message_assets.py:296 19 90
products/warehouse_sources/backend/temporal/data_imports/sources/adroll/adroll.py:14 products/warehouse_sources/backend/temporal/data_imports/sources/freshservice/freshservice.py:18 19 89
products/warehouse_sources/backend/temporal/data_imports/sources/canvas_lms/canvas_lms.py:21 products/warehouse_sources/backend/temporal/data_imports/sources/freshservice/freshservice.py:10 11 88
products/warehouse_sources/backend/temporal/data_imports/sources/blogger/blogger.py:19 products/warehouse_sources/backend/temporal/data_imports/sources/float_app/float_app.py:15 12 87
products/warehouse_sources/backend/temporal/data_imports/sources/cloudinary/cloudinary.py:66 products/warehouse_sources/backend/temporal/data_imports/sources/loops/loops.py:86 13 87
products/warehouse_sources/backend/temporal/data_imports/sources/bugherd/bugherd.py:16 products/warehouse_sources/backend/temporal/data_imports/sources/freshchat/freshchat.py:7 11 86
products/warehouse_sources/backend/temporal/data_imports/sources/freshservice/freshservice.py:2 products/warehouse_sources/backend/temporal/data_imports/sources/opn_payments/opn_payments.py:1 13 86
products/workflows/backend/presentation/views/message_assets.py:118 products/workflows/backend/presentation/views/message_assets.py:154 23 85
products/warehouse_sources/backend/temporal/data_imports/sources/assemblyai/assemblyai.py:134 products/warehouse_sources/backend/temporal/data_imports/sources/front/front.py:247 15 82
products/workflows/backend/presentation/views/hog_flow.py:6308 products/workflows/backend/presentation/views/hog_flow.py:6346 12 80
products/warehouse_sources/backend/temporal/data_imports/sources/bluesky/bluesky.py:97 products/warehouse_sources/backend/temporal/data_imports/sources/cloudinary/cloudinary.py:67 14 79
posthog/api/llm_prompt.py:120 products/skills/backend/api/skills.py:591 13 78
products/warehouse_sources/backend/temporal/data_imports/sources/clickup/clickup.py:249 products/warehouse_sources/backend/temporal/data_imports/sources/freshchat/freshchat.py:285 11 78
products/warehouse_sources/backend/temporal/data_imports/sources/faire/faire.py:1 products/warehouse_sources/backend/temporal/data_imports/sources/float_app/float_app.py:3 12 78
products/warehouse_sources/backend/temporal/data_imports/sources/freshdesk/source.py:108 products/warehouse_sources/backend/temporal/data_imports/sources/shipmail/source.py:82 16 77
products/warehouse_sources/backend/temporal/data_imports/external_product_hooks.py:270 products/warehouse_sources/backend/temporal/data_imports/external_product_hooks.py:312 11 76
products/warehouse_sources/backend/temporal/data_imports/sources/aircall/aircall.py:341 products/warehouse_sources/backend/temporal/data_imports/sources/freshdesk/freshdesk.py:239 11 75
products/warehouse_sources/backend/temporal/data_imports/pipelines/pipeline_v3/postgres_queue/test_consumer.py:99 products/warehouse_sources_queue/backend/testing.py:128 18 73
products/mcp_analytics/backend/hogql_queries/model_breakdown.py:13 products/mcp_analytics/backend/hogql_queries/protocol_version_breakdown.py:13 18 72
products/workflows/backend/presentation/views/workflow_scout_runs.py:54 products/workflows/backend/presentation/views/workflow_tasks.py:41 12 72
products/signals/backend/scout_harness/tools/report.py:1516 products/signals/backend/scout_harness/tools/report.py:1675 12 71
products/signals/backend/scout_harness/tools/report.py:1531 products/signals/backend/scout_harness/tools/report.py:1690 16 70
products/workflows/backend/presentation/views/hog_flow.py:7080 products/workflows/backend/presentation/views/hog_flow.py:7171 11 70
⚠️ Duplication (TypeScript) — 36 new duplicated blocks (worst 498 tokens)

New TypeScript code duplication introduced by this branch. Fails at 70+ tokens in app code, or 150+ tokens when both copies live in test files. Advisory while the gate proves itself: extract a shared helper instead of copying.

First copy Second copy Lines Tokens
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/groupedAccessControlRuleModalLogic.ts:981 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/groupedAccessControlRuleModalLogic.ts:1738 247 498
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/accessControlsLogic.ts:381 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/groupedAccessControlRuleModalLogic.ts:546 134 293
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/groupedAccessControlRuleModalLogic.ts:1106 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/groupedAccessControlRuleModalLogic.ts:1603 140 284
frontend/src/layout/scenes/ConfigureHomeModalContent.tsx:89 frontend/src/layout/scenes/HomepageConfiguration.tsx:99 67 282
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/accessDetailLogic.ts:209 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/addObjectOverrideModalLogic.ts:180 128 264
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/accessDetailLogic.ts:210 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/groupedAccessControlRuleModalLogic.ts:693 126 257
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/groupedAccessControlRuleModalLogic.ts:1104 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/groupedAccessControlRuleModalLogic.ts:1352 127 257
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/accessControlsLogic.ts:721 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/accessControlsLogic.ts:848 124 248
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/groupedAccessControlRuleModalLogic.ts:22 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/groupedAccessControlRuleModalLogic.ts:1230 123 248
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/addObjectOverrideModalLogic.ts:59 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/groupedAccessControlRuleModalLogic.ts:417 123 247
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/addObjectOverrideModalLogic.ts:59 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/accessControlLogic.ts:666 123 247
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/groupedAccessControlRuleModalLogic.ts:982 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/groupedAccessControlRuleModalLogic.ts:1480 123 247
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/accessControlsLogic.ts:389 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/groupedAccessControlRuleModalLogic.ts:982 122 246
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/addObjectOverrideModalLogic.ts:59 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/groupedAccessControlRuleModalLogic.ts:158 123 246
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/addObjectOverrideModalLogic.ts:59 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/groupedAccessControlRuleModalLogic.ts:1106 123 246
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/addObjectOverrideModalLogic.ts:59 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/accessControlLogic.ts:314 123 246
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/addObjectOverrideModalLogic.ts:59 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/groupedAccessControlRuleModalLogic.ts:22 122 245
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/addObjectOverrideModalLogic.ts:59 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/groupedAccessControlRuleModalLogic.ts:281 122 245
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/accessControlsLogic.ts:389 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/groupedAccessControlRuleModalLogic.ts:820 121 244
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/addObjectOverrideModalLogic.ts:739 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/accessControlLogic.ts:542 122 244
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/accessDetailLogic.ts:214 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/addObjectOverrideModalLogic.ts:60 122 243
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/accessDetailLogic.ts:214 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/addObjectOverrideModalLogic.ts:461 122 243
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/accessDetailLogic.ts:214 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/addObjectOverrideModalLogic.ts:590 122 243
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/accessDetailLogic.ts:214 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/addObjectOverrideModalLogic.ts:740 122 243
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/accessDetailLogic.ts:214 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/addObjectOverrideModalLogic.ts:331 121 242
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/accessControlsLogic.ts:389 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/accessControlsLogic.ts:722 121 241
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/accessControlsLogic.ts:390 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/accessControlsLogic.ts:568 120 240
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/accessControlsLogic.ts:390 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/accessDetailLogic.ts:215 120 240
products/business_knowledge/frontend/scenes/settings/businessKnowledgeSettingsLogic.test.ts:10 products/data_catalog/frontend/certificationsLogic.test.ts:17 23 158
products/business_knowledge/frontend/scenes/settings/businessKnowledgeSettingsLogic.test.ts:10 products/data_quality/frontend/dataQualityGateLogic.test.ts:14 23 158
frontend/src/layout/scenes/ConfigureHomeModalContent.tsx:1 frontend/src/layout/scenes/HomepageConfiguration.tsx:1 37 139
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/accessControlsLogic.ts:476 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/groupedAccessControlRuleModalLogic.ts:641 40 80
frontend/src/layout/panel-layout/navbar/tabs/navProductsTabLogic.ts:139 products/workflows/frontend/TemplateLibrary/newTemplateAgentLogic.ts:64 22 76
frontend/src/layout/panel-layout/navbar/tabs/navProductsTabLogic.ts:140 frontend/src/lib/memory/autofillReleaseLogic.ts:10 21 75
products/ai_observability/frontend/components/GenerationEvalRunsTable.tsx:21 products/ai_observability/frontend/evaluations/components/EvaluationRunsTable.tsx:97 11 74
frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/accessDetailLogic.ts:300 frontend/src/layout/navigation-3000/sidepanel/panels/access_control/ResourceAccessControlsV2/addObjectOverrideModalLogic.ts:271 38 73
⚠️ Bundle size — 🔺 +89.8 KiB (+0.1%)

Uncompressed size of every built .js bundle, compared against the base branch.

Total: 68.97 MiB · 🔺 +89.8 KiB (+0.1%)

File Size Δ vs base
posthog-app/_parent/products/business_knowledge/frontend/scenes/BusinessKnowledgePlaygroundScene.js removed 🟢 -26.2 KiB (-100.0%)
posthog-app/_parent/products/business_knowledge/frontend/scenes/playground/BusinessKnowledgePlaygroundScene.js 26.1 KiB 🔺 +26.1 KiB (new)
render-query/src/render-query/render-query.js 20.18 MiB 🔺 +19.9 KiB (+0.1%)
posthog-app/_parent/products/business_knowledge/frontend/scenes/BusinessKnowledgeScene.js removed 🟢 -18.7 KiB (-100.0%)
posthog-app/_parent/products/business_knowledge/frontend/scenes/sources/BusinessKnowledgeScene.js 18.6 KiB 🔺 +18.6 KiB (new)
posthog-app/_parent/products/business_knowledge/frontend/scenes/KnowledgeSourceScene.js removed 🟢 -18.0 KiB (-100.0%)
posthog-app/_parent/products/business_knowledge/frontend/scenes/source/KnowledgeSourceScene.js 18.0 KiB 🔺 +18.0 KiB (new)
posthog-app/_parent/products/business_knowledge/frontend/scenes/BusinessKnowledgeSettingsScene.js removed 🟢 -16.8 KiB (-100.0%)
posthog-app/_parent/products/business_knowledge/frontend/scenes/settings/BusinessKnowledgeSettingsScene.js 16.8 KiB 🔺 +16.8 KiB (new)
posthog-app/_parent/products/signals/frontend/inbox/InboxScene.js 491.0 KiB 🔺 +16.1 KiB (+3.4%)
posthog-app/_parent/products/data_modeling/frontend/nodeDetail/NodeDetailScene.js 33.3 KiB 🔺 +8.0 KiB (+31.6%)
exporter/src/queries/schema.js 1.23 MiB 🔺 +7.6 KiB (+0.6%)
posthog-app/src/queries/schema.js 1.23 MiB 🔺 +7.6 KiB (+0.6%)
posthog-app/_parent/products/ai_observability/frontend/evaluations/AIObservabilityEvaluation.js 87.0 KiB 🔺 +5.9 KiB (+7.3%)
posthog-app/src/scenes/AuthenticatedShell.js 254.1 KiB 🔺 +5.7 KiB (+2.3%)
posthog-app/src/scenes/notebooks/NotebookScene.js 37.0 KiB 🔺 +5.6 KiB (+17.8%)
posthog-app/_parent/products/mcp_analytics/frontend/MCPAnalyticsScene.js 185.5 KiB 🔺 +4.5 KiB (+2.5%)
posthog-app/_parent/products/workflows/frontend/Broadcasts/BroadcastScene.js 67.3 KiB 🔺 +3.7 KiB (+5.9%)
posthog-app/_parent/products/stamphog/frontend/scenes/StamphogDigestsScene/StamphogDigestsScene.js 9.5 KiB 🔺 +1.6 KiB (+20.7%)
posthog-app/_parent/products/stamphog/frontend/scenes/StamphogScene/StamphogScene.js 22.1 KiB 🔺 +1.1 KiB (+5.4%)

Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report

✅ Eager graph — within budget

How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.58 MiB · 22 files 🔺 +330 B (+0.0%) █████████░ 85.8% of 1.84 MiB
logged-out boot: index + App + bootApp (preloaded by every page, including /login)
src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
3.52 MiB · 629 files 🔺 +3.0 KiB (+0.1%) █████████░ 87.4% of 4.03 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
7.35 MiB · 2,339 files 🔺 +13.2 KiB (+0.2%) █████████░ 88.1% of 8.34 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/layout/navigation-3000/navigationLogic.tsx stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/scenes/dashboard/dashboardLogic.tsx stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/lemon-ui/LemonMarkdown/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/components/RichContentEditor/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/components/CodeSnippet/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/taxonomy/core-filter-definitions-by-group.json stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx

Largest files eagerly shipped from src/index.tsx
Size File
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
6.3 KiB ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
4.5 KiB ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js
3.9 KiB ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js
1.4 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.3 KiB src/index.tsx
1.3 KiB src/RootErrorBoundary.tsx
912 B ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
854 B src/scenes/ChunkLoadErrorBoundary.tsx
Largest files eagerly shipped from src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
Size File
301.8 KiB ../node_modules/.pnpm/posthog-js@1.434.14_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
216.9 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
100.5 KiB src/lib/api.ts
88.5 KiB src/products.tsx
69.4 KiB src/lib/lemon-ui/icons/icons.tsx
40.1 KiB src/lib/utils/eventUsageLogic.ts
38.7 KiB ../node_modules/.pnpm/@dnd-kit+core@6.0.8_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@dnd-kit/core/dist/core.esm.js
33.9 KiB ../node_modules/.pnpm/kea@4.0.0-pre.6_patch_hash=139b8d1f1304f9d9da452a9a1244c94ea679dbcb85687d8999563146879fb6f5_react@18.3.1/node_modules/kea/lib/index.cjs.js
28.4 KiB src/scenes/scenes.ts
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
301.8 KiB ../node_modules/.pnpm/posthog-js@1.434.14_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
272.3 KiB src/taxonomy/core-filter-definitions-by-group.json
216.9 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
153.7 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
100.5 KiB src/lib/api.ts
98.8 KiB ../packages/quill/packages/quill/dist/index.js
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
90.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
88.5 KiB src/products.tsx

Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479

✅ Toolbar bundle — eager 2.16 MiB within budget

What the toolbar ships to customer pages, measured from the esbuild output (minified, post-tree-shake). The eager set is the entry plus everything statically imported from it — fetched before any feature runs; deferred chunks load lazily. The eager guardrail is 5.72 MiB. Each output file must also stay below 10 MB, where CloudFront stops compressing it. The module boundary is enforced separately by check-toolbar-graph.

Metric Size Δ vs base Budget
Eager (shipped)
entry + static imports
2.16 MiB · 19 files 🔺 +478 B (+0.0%) ████░░░░░░ 37.8% of 5.72 MiB
Deferred (lazy) 2.10 MiB · 44 files 🔺 +588 B (+0.0%) n/a — loads on demand
Loader dist/toolbar.js 1.2 KiB no change █░░░░░░░░░ 6.0% of 19.5 KiB
Largest eagerly-shipped chunks
Size File
805.5 KiB dist/toolbar/toolbar-app-ZATDHZQQ.css
651.7 KiB dist/toolbar/chunk-chunk-7ZORLPSY.js
259.4 KiB dist/toolbar/chunk-chunk-2FGAEFTI.js
138.3 KiB dist/toolbar/chunk-chunk-UA3V2PCC.js
131.8 KiB dist/toolbar/chunk-chunk-FDH2IBXT.js
75.2 KiB dist/toolbar/toolbar-app-SD4VQXWA.js
69.0 KiB dist/toolbar/chunk-chunk-TSAL54PB.js
35.6 KiB dist/toolbar/chunk-chunk-63EWNJRZ.js
21.0 KiB dist/toolbar/chunk-chunk-F5GDSM5D.js
6.8 KiB dist/toolbar/chunk-chunk-DV7IWQNF.js

Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile

✅ Dist folder size — 🔺 +1.38 MiB (+0.1%)

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 947.83 MiB · 🔺 +1.38 MiB (+0.1%)

ℹ️ MCP UI apps size — 33 app(s), 17633.1 KB JS

Built size of each MCP UI app (main.js + styles.css).

App JS CSS
debug 597.9 KB 199.2 KB
action 454.1 KB 199.2 KB
action-list 564.2 KB 199.2 KB
cohort 453.1 KB 199.2 KB
cohort-list 563.2 KB 199.2 KB
email-template 452.9 KB 199.2 KB
error-details 469.6 KB 199.2 KB
error-issue 454.5 KB 199.2 KB
error-issue-list 564.8 KB 199.2 KB
experiment 561.3 KB 199.2 KB
experiment-list 564.9 KB 199.2 KB
experiment-results 566.3 KB 199.2 KB
feature-flag 566.8 KB 199.2 KB
feature-flag-list 570.5 KB 199.2 KB
feature-flag-testing 457.3 KB 199.2 KB
inline-scan 453.6 KB 199.2 KB
insight-actors 562.3 KB 199.2 KB
invite-email-preview 452.3 KB 199.2 KB
llm-costs 559.3 KB 199.2 KB
session-recording 455.3 KB 199.2 KB
survey 454.7 KB 199.2 KB
survey-global-stats 561.9 KB 199.2 KB
survey-list 564.9 KB 199.2 KB
survey-stats 561.9 KB 199.2 KB
trace-span 453.5 KB 199.2 KB
trace-span-list 564.1 KB 199.2 KB
vision-observation-list 563.3 KB 199.2 KB
workflow 453.4 KB 199.2 KB
workflow-list 563.5 KB 199.2 KB
loops-review 457.8 KB 199.2 KB
query-results 774.1 KB 199.2 KB
render-ui 858.1 KB 199.2 KB
visual-review-snapshots 457.9 KB 199.2 KB

@greptile-apps

greptile-apps Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Retrigger

[Medium risk] Adds listing, refresh, and dismiss operations for content opportunities.

The PR does not appear safe to merge while dismissed opportunities remain visible in list responses.

Reviews (2) · Last reviewed commit: "fix(web-analytics): describe what dismis..."

Comment on lines +654 to +658
queryset = ContentAutopilotOpportunity.objects.for_team(self.team_id).filter(profile__deleted=False)
if self.action == "list":
filters = self.validated_query_params(ContentAutopilotOpportunityListQuerySerializer)
queryset = queryset.filter(profile_id=filters["profile_id"])
return queryset.order_by("-score", "title")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Dismissed opportunities remain visible

After a user dismisses an opportunity, this list query still returns it because it does not filter out dismissed rows. The refresh response also returns dismissed rows, so dismissal does not hide the opportunity as the API promises. Exclude dismissed opportunities from both responses and test a list request made after dismissal.

Prompt To Fix With AI
This is a comment left during a code review.
Path: products/web_analytics/backend/presentation/views/content_autopilot.py
Line: 654-658

Comment:
**Dismissed opportunities remain visible**

After a user dismisses an opportunity, this list query still returns it because it does not filter out dismissed rows. The refresh response also returns dismissed rows, so dismissal does not hide the opportunity as the API promises. Exclude dismissed opportunities from both responses and test a list request made after dismissal.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@coderabbitai

coderabbitai Bot commented Sep 29, 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: 4be5d56c-cc5c-4eac-89eb-3bd9a800c9a2

📥 Commits

Reviewing files that changed from the base of the PR and between 660933e and 12039a1.

⛔ Files ignored due to path filters (1)
  • products/web_analytics/frontend/generated/api.ts is excluded by !**/generated/**
📒 Files selected for processing (2)
  • products/web_analytics/backend/presentation/views/content_autopilot.py
  • products/web_analytics/backend/test/test_content_autopilot_api.py

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


📝 Walkthrough

Walkthrough

The change adds team-scoped opportunity listing, refresh, and dismissal endpoints. It adds serializers and generated API types for opportunities, engine answers, and citation-gap data. The route and disabled MCP tools expose the operations. Tests cover profile filtering, dismissal, and team scoping.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to 12039

The opportunity API is gated and team-scoped, and dismissed opportunities intentionally remain visible with their status. No identified issue blocks merging.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 12039

The new endpoints broaden access to opportunity data and refresh controls, but the reviewed paths preserve team scoping, a feature flag, and write authorization for mutations. No material security boundary violation was established.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The new reachable surface is the flagged project API for opportunity records and refresh work. Reviewed lookups constrain records to the caller's team; broader deployment-level effects were not established.

Trust Boundaries and Controls

  • observed — Caller-supplied profile and opportunity identifiers are checked against team-scoped records. Dismissal rejects an opportunity being actively drafted and uses a row lock for its status transition.

Resilience and Maintainability Implications

  • observed — Refresh preserves actively drafted opportunities and does not delete dismissed opportunities as stale; its database changes occur within an atomic block. These controls limit the effect of interruption or repetition, but do not order overlapping source snapshots.

Hardening Proposals

  • proposed — If opportunity freshness must be monotonic under concurrent API requests, serialize refreshes per profile or reject commits based on an older source generation.
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description follows the required template. It explains the problem, user-visible changes, testing coverage and limits, release status, documentation status, and agent context. It is sufficiently d…
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

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

Review profile: QUIET

Plan: Enterprise

Run ID: f565f843-7eb1-4e14-bf93-c785c5c33bae

📥 Commits

Reviewing files that changed from the base of the PR and between 7dbe926 and 660933e.

⛔ Files ignored due to path filters (3)
  • products/web_analytics/frontend/generated/api.schemas.ts is excluded by !**/generated/**
  • products/web_analytics/frontend/generated/api.ts is excluded by !**/generated/**
  • products/web_analytics/frontend/generated/api.zod.ts is excluded by !**/generated/**
📒 Files selected for processing (8)
  • posthog/test/repo_invariants/test_pagination_contract.py
  • products/web_analytics/backend/facade/content_autopilot.py
  • products/web_analytics/backend/presentation/views/content_autopilot.py
  • products/web_analytics/backend/routes.py
  • products/web_analytics/backend/test/test_content_autopilot_api.py
  • products/web_analytics/backend/test/test_content_autopilot_lifecycle.py
  • products/web_analytics/mcp/tools.yaml
  • services/mcp/src/api/generated.ts

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

queryset = ContentAutopilotOpportunity.objects.for_team(self.team_id).filter(profile__deleted=False)
if self.action == "list":
filters = self.validated_query_params(ContentAutopilotOpportunityListQuerySerializer)
queryset = queryset.filter(profile_id=filters["profile_id"])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Exclude dismissed opportunities from both list responses.

After dismiss_opportunity sets an opportunity’s status to DISMISSED, both the list query and the refresh query still return it. The dismiss action says it hides the opportunity from the list. Filter DISMISSED rows from both queries while keeping detail lookup available for the dismissal response. (raw.githubusercontent.com)

Also applies to: 674-674

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@jordanm-posthog
jordanm-posthog marked this pull request as ready for review September 29, 2026 18:42
@github-actions
github-actions Bot requested a deployment to preview-pr-108593 September 29, 2026 18:43 In progress
@github-actions

Copy link
Copy Markdown
Contributor

🦔 Hogbox preview · 🔧 building…

Spinning up PostHog for this PR on a hogland hogbox — this comment updates in place when it's ready (usually a few minutes).

commit 12039a1 · build log

@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested a review from a team September 29, 2026 18:43

This branch is being deployed

1 in progress deployment
preview-pr-108593 — 12039a1d Deployed Sep 29, 2026 by github-actions[bot]
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