Skip to content

fix(flags): make recent feature flags show and link correctly - #110227

Open
phillram wants to merge 5 commits into
masterfrom
posthog/fix-recent-flag-labels
Open

phillram wants to merge 5 commits into
masterfrom
posthog/fix-recent-flag-labels

Conversation

@phillram

@phillram phillram commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Problem

A survey author who links a feature flag sees blank rows in the flag picker's Recent category: no key, no name, nothing to tell one flag from another. The flags themselves are fine, and the main Feature Flags list labels them correctly.

A feature flag keeps its description in name (a legacy misnomer), and most flags leave the description empty. The picker's Recent category had nothing else to label a row with:

  • A pick stored as a recent kept only name and id, so the flag key never reached storage.
  • The recent row labelled itself from name alone and never asked the source group for a label.
  • The definition popover titled itself from name too, so it read "(empty string)" for any flag with no description, in the Recent list and in the main flag list.

The picker also handed the stored summary to callers as if it were the flag, which broke the link rather than only its label. A replay trigger persists {id, key} to the team and the key was undefined, so the trigger matched nothing. The survey editor stored the summary as linked_flag, leaving the variant selector with no flag to read. That is why a flag picked from Recent did not take, while the same flag picked from the Feature Flags list did.

Reported in support ticket 76386.

Changes

  • A recent flag row now shows the flag key, and its hover panel titles itself with the key.
  • A stored recent keeps the fields a group labels its rows with (key, title, short_id), through one shared pickMinimalRecentItem used by both the classic picker and the rebuilt menu.
  • Flag recents already stored without a key are dropped from the list, so blank rows go away now instead of waiting out the 30 day expiry.
  • The same gap closes for the other groups that do not label a row with name: a saved replay filter keeps derived_name, a group keeps group_key, a notebook keeps title and short_id.
  • A picked flag also hands its key back to the caller again, which is what FlagSelector labels its button with while the flag loads. That button serves surveys, experiments, early access features, product tours, replay triggers and ingestion controls, and the flag dependency picker on a feature flag reads the same group.
  • A flag picked from Recent now links correctly. pickedFeatureFlag resolves the id, requires a key, and returns the flag only when the picked row really is one; the third onChange argument is optional, so a caller that needs the whole flag loads it by id. The product tour field already did that, and the survey editor now does too.
  • Mechanical: comments in FlagSelector that described the old storage shape.

Before and after, same Storybook story (a flag with no description, picked and then viewed in Recent):

before_wide

after_wide

How did you test this code?

Test rationale: Added cases, one per defect that no existing test covered. pickMinimalRecentItem in recentTaxonomicFiltersLogic.test.ts locks the stored shape, because the nearest existing cases only record items whose label is name. A render case in TaxonomicFilterKeyOnly.test.tsx asserts the Recent row text, which is the only level that catches the row label; it sits beside the other recent-row render cases in that file. One case in suggestedContextFilters.test.ts covers dropping a keyless flag recent.

pickedFeatureFlag is covered in FlagSelector.test.tsx: a summary row withholds the flag, a full row returns it, and a row with no key is not a selection.

Ran locally: the Jest suites for TaxonomicFilter, DefinitionPopover, PropertyFilters, FlagSelector, surveys, the experiment form and ingestion controls, and the repo TypeScript check. Checked the before and after by hand in Storybook through a headless browser, which is where the screenshots come from; the story was scratch and is not in the diff. Not run: the rest of the frontend suite and anything backend, since the change is frontend only.

Release status

  • No feature flag controls this change

Automatic notifications

  • Publish to changelog?

Docs update

No docs cover the picker's Recent category, so no docs change.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Agent: PostHog Desktop, Claude Opus 5

  • Skills invoked: /modifying-taxonomic-filter, /writing-tests, /writing-code-comments, /writing-pr-descriptions.
  • The storage fix alone looked complete until the Storybook check showed the row still blank, which exposed the two display defects. That is why the diff reaches InfiniteList and DefinitionPopoverContents as well.
  • The reporter then said the flag would not take at all when picked from Recent, only that it looked blank. Tracing the picked object through each caller found the second defect, which is the one that broke the link. The display fix alone would have left it.
  • Both recent record sites are changed, so the classic picker and the rebuilt menu stay in step.
  • No duplicate: gh pr list --state open --search over flag and taxonomic recent terms found no open PR for this.
  • Public artifact: the ticket screenshot and the reporter's project were not used in the diff. The flag keys in the tests and screenshots are invented.

Created with PostHog Desktop

A feature flag keeps its description in `name`, and most flags leave it empty, so the taxonomic filter had nothing to label a flag with in its Recent category.

Two causes. A pick stored as a recent kept only `name` and `id`, which dropped the flag key. The recent row then labelled itself from `name` alone and never asked the source group for a label. The definition popover titled itself from `name` too, so it read "(empty string)" for any flag with no description, in the Recent list and in the main flag list.

Recents now keep the fields a group labels its rows with, the recent row falls back to the group's `getName`, and the popover title does the same. Flag recents already stored without a key are dropped, so the blank rows go away instead of waiting out the 30 day expiry.

Generated-By: PostHog Desktop
Task-Id: 1b898a61-0065-4b4d-b7b2-84f843040805
@phillram phillram self-assigned this Oct 1, 2026
@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

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

🤖 CI report

✅ Trunk lane — non-backend lane

This PR is assigned to the non-backend lane. It does not run backend Python tests and may merge in parallel with PRs in other lanes.

⚠️ Complexity (TypeScript) — 17 functions above the limit (max 63)

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.

Function Location Complexity Limit
DefinitionView frontend/src/lib/components/DefinitionPopover/DefinitionPopoverContents.tsx:118 63 10
InfiniteListRow frontend/src/lib/components/TaxonomicFilter/InfiniteList.tsx:437 44 10
SurveyEdit frontend/src/scenes/surveys/SurveyEdit.tsx:265 44 10
renderItemContents frontend/src/lib/components/TaxonomicFilter/InfiniteList.tsx:198 39 10
InfiniteListEmptyState frontend/src/lib/components/TaxonomicFilter/InfiniteList.tsx:707 26 10
<anonymous> frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx:1162 23 10
DefinitionEdit frontend/src/lib/components/DefinitionPopover/DefinitionPopoverContents.tsx:588 21 10
selectItem frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx:2602 20 10
resolveTaxonomicGroupTypes frontend/src/lib/components/TaxonomicFilter/hooks/useTaxonomicFilter.ts:207 18 10
infiniteListResultsReceived frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx:2794 17 10
InfiniteList frontend/src/lib/components/TaxonomicFilter/InfiniteList.tsx:884 16 10
<anonymous> frontend/src/lib/components/TaxonomicFilter/hooks/useTaxonomicFilter.ts:480 15 10
<anonymous> frontend/src/lib/components/DefinitionPopover/DefinitionPopoverContents.tsx:491 14 10
isDuplicateRecentFilter frontend/src/lib/components/TaxonomicFilter/recentTaxonomicFiltersLogic.ts:147 13 10
<anonymous> frontend/src/lib/components/TaxonomicFilter/utils/suggestedContextFilters.ts:31 13 10
ControlledDefinitionPopover frontend/src/lib/components/DefinitionPopover/DefinitionPopoverContents.tsx:714 11 10
expandRecentsForDisplay frontend/src/lib/components/TaxonomicFilter/recentTaxonomicFiltersLogic.ts:104 11 10
✅ Duplication (Python) — clean

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.

✅ Duplication (TypeScript) — clean

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.

🚨 Comment density — 16% of added code lines are comments (45 of 273)

This section warns when comments are more than 3% of the code lines a PR adds, and alerts above 6%. Before agent-assisted PRs, the typical share was about 2%. Only full-line comments count. Docstrings, generated files, snapshots, migrations, and workflow files are left out.

Comments that restate the code, record how the change came about, or narrate the next line add noise for the next reader. Keep the comments that explain a reason the code cannot show, and remove the rest. See .agents/skills/writing-code-comments/SKILL.md for the house rules.

Files with the most added comment lines:

File Comment lines Added lines
frontend/src/lib/components/FlagSelector.tsx 16 43
frontend/src/lib/components/TaxonomicFilter/recentTaxonomicFiltersLogic.ts 7 17
frontend/src/scenes/surveys/SurveyEdit.tsx 7 34
frontend/src/lib/components/DefinitionPopover/DefinitionPopoverContents.tsx 4 5
frontend/src/lib/components/TaxonomicFilter/utils/suggestedContextFilters.ts 4 18
frontend/src/lib/components/FlagSelector.test.tsx 3 59
frontend/src/lib/components/TaxonomicFilter/InfiniteList.tsx 3 4
frontend/src/lib/components/TaxonomicFilter/utils/suggestedContextFilters.test.ts 1 41

This check does not block merging. It updates on every push and clears when the share drops.

✅ Bundle size — 🟢 -323.0 KiB (-0.4%)

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

Total: 69.79 MiB · 🟢 -323.0 KiB (-0.4%)

File Size Δ vs base
render-query/src/render-query/render-query.js 18.76 MiB 🟢 -1.46 MiB (-7.2%)
exporter/src/lib/hog.js 435.1 KiB 🔺 +435.1 KiB (new)
exporter/src/exporter/scenes/ExporterNotebookScene.js 3.90 MiB 🔺 +202.9 KiB (+5.4%)
posthog-app/src/scenes/saved-insights/SavedInsights.js 91.4 KiB 🔺 +79.5 KiB (+668.4%)
exporter/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.60_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/react-shadow.esm-B4-N-ucD.js 76.7 KiB 🔺 +76.7 KiB (new)
posthog-app/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.60_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/react-shadow.esm-B4-N-ucD.js 76.7 KiB 🔺 +76.7 KiB (new)
exporter/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.57_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/react-shadow.esm-CAbukJW8.js removed 🟢 -76.7 KiB (-100.0%)
posthog-app/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.57_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/react-shadow.esm-CAbukJW8.js removed 🟢 -76.7 KiB (-100.0%)
exporter/_parent/products/subscriptions/frontend/components/Subscriptions/SubscriptionsModal.js 74.3 KiB 🔺 +74.3 KiB (new)
posthog-app/_parent/products/canvas/frontend/scene/CanvasScene.js 69.1 KiB 🔺 +69.1 KiB (new)
posthog-app/src/scenes/project-homepage/ProjectHomepage.js 1.6 KiB 🟢 -56.8 KiB (-97.2%)
posthog-app/src/scenes/AuthenticatedShell.js 268.1 KiB 🟢 -48.0 KiB (-15.2%)
exporter/_parent/products/posthog_ai/frontend/scenes/TaskTracker/TaskTracker.js 92.2 KiB 🔺 +46.4 KiB (+101.5%)
posthog-app/_parent/products/canvas/frontend/sidePanel/CanvasSidePanel.js 45.9 KiB 🔺 +45.9 KiB (new)
posthog-app/src/scenes/project-homepage/ai-first/AiFirstHomepage.js 43.4 KiB 🔺 +43.4 KiB (new)
posthog-app/_parent/products/posthog_ai/frontend/scenes/TaskTracker/TaskTracker.js 91.9 KiB 🔺 +38.3 KiB (+71.4%)
posthog-app/src/layout/today/TodaySpacesPane.js 31.9 KiB 🔺 +31.9 KiB (new)
infrastructure-admin/_parent/products/tasks/frontend/infrastructure/infrastructure-admin.js 606.9 KiB 🔺 +28.6 KiB (+4.9%)
posthog-app/_parent/products/dashboards/frontend/widgets/AddWidgetModal.js 24.2 KiB 🔺 +24.2 KiB (new)
exporter/_parent/products/dashboards/frontend/widgets/AddWidgetModal.js 22.0 KiB 🔺 +22.0 KiB (new)
posthog-app/_parent/products/ai_observability/frontend/scoreDefinitions/AIObservabilityScorerScene.js 21.6 KiB 🔺 +21.6 KiB (new)
posthog-app/_parent/products/posthog_ai/frontend/scenes/TaskTracker/components/ArtifactObjectEmbed.js removed 🟢 -19.5 KiB (-100.0%)
posthog-app/src/scenes/project-homepage/today/TodayReportPage.js 19.0 KiB 🔺 +19.0 KiB (new)
posthog-app/_parent/products/data_modeling/frontend/ModelsScene.js 58.3 KiB 🔺 +18.8 KiB (+47.5%)
exporter/src/lib/components/TerraformExporter/TerraformExportModal.js 17.3 KiB 🔺 +17.3 KiB (new)
posthog-app/src/scenes/views/Views.js 10.4 KiB 🔺 +10.4 KiB (new)
posthog-app/src/scenes/project-homepage/today/TodayHome.js 10.3 KiB 🔺 +10.3 KiB (new)
posthog-app/src/scenes/project-homepage/today/TodayReportHoverCardChart.js 10.2 KiB 🔺 +10.2 KiB (new)
posthog-app/_parent/products/replay_vision/frontend/replay_scanners/components/ScannerScoutsTab.js 18.9 KiB 🟢 -10.1 KiB (-34.9%)
posthog-app/src/scenes/web-analytics/WebAnalyticsScene.js 253.6 KiB 🔺 +10.0 KiB (+4.1%)
posthog-app/_parent/products/tasks/frontend/spaces/NewSessionScene.js 9.4 KiB 🔺 +9.4 KiB (new)
posthog-app/src/scenes/dashboard/addInsightToDashboardModal/AddInsightToDashboardModal.js 8.6 KiB 🔺 +8.6 KiB (new)
posthog-app/src/layout/today/TodayViewsSidebar.js 8.5 KiB 🔺 +8.5 KiB (new)
posthog-app/src/layout/today/TodaySessionHoverCard.js 8.3 KiB 🔺 +8.3 KiB (new)
toolbar/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.60_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/chunk-react-shadow.esm-B4-N-ucD.js 7.8 KiB 🔺 +7.8 KiB (new)
toolbar/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.57_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/chunk-react-shadow.esm-CAbukJW8.js removed 🟢 -7.8 KiB (-100.0%)
posthog-app/_parent/products/posthog_ai/frontend/components/tool/widgets/SearchSessionRecordingsWidget.js 4.7 KiB 🟢 -7.6 KiB (-62.1%)
posthog-app/_parent/products/ai_observability/frontend/scoreDefinitions/AIObservabilityScorersScene.js 13.6 KiB 🟢 -7.5 KiB (-35.6%)
posthog-app/src/scenes/insights/InsightQuickStart/InsightQuickStart.js 9.3 KiB 🟢 -7.2 KiB (-43.8%)
posthog-app/_parent/products/customer_analytics/frontend/scenes/CustomerJourneyTemplatesScene/CustomerJourneyTemplatesScene.js 11.3 KiB 🟢 -7.1 KiB (-38.6%)
exporter/_parent/products/posthog_ai/frontend/scenes/TaskTracker/components/ArtifactObjectEmbed.js removed 🟢 -6.8 KiB (-100.0%)
posthog-app/src/scenes/project-homepage/today/TodayReportHoverCard.js 6.8 KiB 🔺 +6.8 KiB (new)
exporter/src/scenes/dashboard/addInsightToDashboardModal/AddInsightToDashboardModal.js 6.7 KiB 🔺 +6.7 KiB (new)
posthog-app/src/layout/today/TodaySpaceHoverCard.js 6.7 KiB 🔺 +6.7 KiB (new)
posthog-app/src/scenes/activity/explore/EventDetails.js 6.5 KiB 🔺 +6.5 KiB (new)
posthog-app/_parent/products/tasks/frontend/spaces/NewSpaceDialog.js 5.5 KiB 🔺 +5.5 KiB (new)
posthog-app/src/lib/components/ActivityLog/describers.js 162.5 KiB 🟢 -5.3 KiB (-3.1%)
posthog-app/_parent/products/ai_observability/frontend/aiObservabilityColumnRenderers.js 5.2 KiB 🔺 +5.2 KiB (new)
posthog-app/_parent/products/subscriptions/frontend/components/Subscriptions/SubscriptionsModal.js 5.0 KiB 🔺 +5.0 KiB (new)
posthog-app/src/scenes/project-homepage/today/TodayReportHoverCardMetric.js 4.9 KiB 🔺 +4.9 KiB (new)
posthog-app/_parent/products/canvas/frontend/newCanvas/CanvasNewScene.js 4.3 KiB 🔺 +4.3 KiB (new)
posthog-app/src/lib/components/Sharing/SharingModal.js 3.8 KiB 🔺 +3.8 KiB (new)
posthog-app/_parent/products/business_knowledge/frontend/scenes/settings/BusinessKnowledgeSettingsScene.js 13.1 KiB 🟢 -3.7 KiB (-22.0%)
posthog-app/_parent/products/conversations/frontend/scenes/settings/SupportSettingsScene.js 12.3 KiB 🟢 -3.7 KiB (-23.0%)
posthog-app/src/scenes/settings/SettingsMap.js 12.2 KiB 🟢 -3.7 KiB (-23.1%)
posthog-app/src/scenes/settings/SettingsScene.js 15.8 KiB 🟢 -3.7 KiB (-18.9%)
posthog-app/_parent/products/customer_analytics/frontend/scenes/CustomerAnalyticsConfigurationScene/CustomerAnalyticsConfigurationScene.js 14.4 KiB 🟢 -3.6 KiB (-20.1%)
exporter/src/scenes/activity/explore/EventDetails.js 3.6 KiB 🔺 +3.6 KiB (new)
posthog-app/_parent/products/error_tracking/frontend/posthogAi/ErrorTrackingWidget.js 13.5 KiB 🟢 -3.5 KiB (-20.5%)
posthog-app/_parent/products/conversations/frontend/scenes/ticket/SupportTicketScene.js 70.3 KiB 🔺 +3.4 KiB (+5.1%)
posthog-app/_parent/products/replay_vision/frontend/replay_scanners/ScannerEditorScene.js 59.7 KiB 🟢 -3.4 KiB (-5.3%)
posthog-app/_parent/products/visual_review/frontend/scenes/VisualReviewRunScene.js 61.1 KiB 🔺 +3.3 KiB (+5.6%)
posthog-app/_parent/products/tasks/frontend/spaces/SpaceScene.js 71.6 KiB 🟢 -3.2 KiB (-4.3%)
posthog-app/src/scenes/debug/hog/HogRepl.js 8.5 KiB 🟢 -3.1 KiB (-26.9%)
posthog-app/src/scenes/dashboard/Dashboard.js 11.5 KiB 🟢 -3.1 KiB (-21.0%)
exporter/_parent/products/ai_observability/frontend/aiObservabilityColumnRenderers.js 3.0 KiB 🔺 +3.0 KiB (new)
posthog-app/src/scenes/dashboard/dashboards/Dashboards.js 38.7 KiB 🟢 -2.9 KiB (-6.9%)
posthog-app/_parent/products/mcp_analytics/frontend/MCPAnalyticsScene.js 187.4 KiB 🔺 +2.5 KiB (+1.4%)
posthog-app/src/scenes/notebooks/NotebookScene.js 34.7 KiB 🟢 -2.4 KiB (-6.5%)
exporter/src/lib/components/Sharing/SharingModal.js 2.3 KiB 🔺 +2.3 KiB (new)
posthog-app/_parent/products/conversations/frontend/components/ComposeTicket/ComposeTicketModal.js 3.5 KiB 🔺 +2.1 KiB (+153.2%)
posthog-app/src/queries/nodes/WebVitals/WebVitals.js 15.9 KiB 🟢 -2.1 KiB (-11.8%)
posthog-app/src/queries/Query/Query.js 9.6 KiB 🟢 -2.1 KiB (-18.2%)
posthog-app/src/scenes/web-analytics/SessionAttributionExplorer/SessionAttributionExplorerScene.js 15.4 KiB 🟢 -2.1 KiB (-12.0%)
posthog-app/_parent/products/persons/frontend/pages/PersonsScene.js 14.9 KiB 🟢 -2.1 KiB (-12.2%)
posthog-app/src/layout/today/TodayToolsSidebar.js 2.1 KiB 🔺 +2.1 KiB (new)
posthog-app/_parent/products/posthog_ai/frontend/components/tool/widgets/CreateInsightWidget.js 10.3 KiB 🟢 -2.1 KiB (-16.6%)
posthog-app/_parent/products/posthog_ai/frontend/components/tool/widgets/QueryWidget.js 10.4 KiB 🟢 -2.1 KiB (-16.6%)
posthog-app/src/layout/today/TodayChatHoverCard.js 2.0 KiB 🔺 +2.0 KiB (new)
posthog-app/src/scenes/debug/DebugScene.js 28.3 KiB 🟢 -1.9 KiB (-6.4%)
posthog-app/_parent/products/groups/frontend/pages/Groups.js 12.9 KiB 🟢 -1.9 KiB (-13.0%)
posthog-app/_parent/products/signals/frontend/inbox/components/detail/ReportAiPanel.js 13.3 KiB 🟢 -1.9 KiB (-12.7%)
posthog-app/src/scenes/activity/explore/SessionsScene.js 13.3 KiB 🟢 -1.9 KiB (-12.6%)
posthog-app/_parent/products/customer_analytics/frontend/scenes/CustomerJourneyBuilderScene/CustomerJourneyBuilderScene.js 11.1 KiB 🟢 -1.9 KiB (-14.6%)
posthog-app/src/layout/panel-layout/navbar/tabs/NavTabChat.js 19.4 KiB 🟢 -1.9 KiB (-8.9%)
posthog-app/src/layout/today/TodayLibrarySidebar.js 1.8 KiB 🔺 +1.8 KiB (new)
posthog-app/_parent/products/posthog_ai/frontend/scenes/TaskTracker/components/SidePanelRunnerImpl.js 15.9 KiB 🟢 -1.8 KiB (-10.3%)
posthog-app/_parent/products/posthog_ai/frontend/scenes/TaskTracker/components/EmbeddedTaskComposerImpl.js 11.1 KiB 🟢 -1.8 KiB (-14.1%)
posthog-app/src/scenes/cohorts/Cohort.js 44.1 KiB 🟢 -1.8 KiB (-4.0%)
posthog-app/src/scenes/experiments/SharedMetrics/SharedMetric.js 18.7 KiB 🟢 -1.8 KiB (-8.8%)
exporter/_parent/products/posthog_ai/frontend/components/tool/widgets/SearchSessionRecordingsWidget.js 3.1 KiB 🟢 -1.8 KiB (-36.8%)
posthog-app/src/scenes/marketing-analytics/MarketingAnalyticsScene.js 135.4 KiB 🟢 -1.8 KiB (-1.3%)
exporter/_parent/products/alerts/frontend/views/EditAlertModal.js 114.5 KiB 🔺 +1.7 KiB (+1.5%)
posthog-app/_parent/products/endpoints/frontend/EndpointsScene.js 37.2 KiB 🟢 -1.7 KiB (-4.4%)
posthog-app/_parent/products/replay_vision/frontend/replay_scanners/ReplayScannersScene.js 99.8 KiB 🔺 +1.7 KiB (+1.7%)
posthog-app/_parent/products/ai_observability/frontend/playground/AIObservabilityPlaygroundScene.js 46.6 KiB 🔺 +1.7 KiB (+3.7%)
posthog-app/src/scenes/product-tours/ProductTour.js 179.8 KiB 🟢 -1.7 KiB (-0.9%)
posthog-app/_parent/products/ai_observability/frontend/tags/AIObservabilityTagScene.js 36.9 KiB 🟢 -1.7 KiB (-4.3%)
posthog-app/_parent/products/business_knowledge/frontend/scenes/playground/BusinessKnowledgePlaygroundScene.js 25.1 KiB 🟢 -1.6 KiB (-6.1%)
posthog-app/_parent/products/ai_observability/frontend/tags/AIObservabilityTagsScene.js 16.9 KiB 🟢 -1.6 KiB (-8.8%)
posthog-app/_parent/products/ai_observability/frontend/evaluations/EvaluationsScene.js 29.0 KiB 🟢 -1.6 KiB (-5.3%)
posthog-app/_parent/products/endpoints/frontend/EndpointScene.js 61.3 KiB 🟢 -1.6 KiB (-2.6%)
posthog-app/_parent/products/customer_analytics/frontend/CustomerAnalyticsScene.js 11.6 KiB 🟢 -1.6 KiB (-12.1%)
posthog-app/_parent/products/feature_flags/frontend/staff/FeatureFlagsStaffToolsScene.js 60.6 KiB 🔺 +1.5 KiB (+2.5%)
posthog-app/_parent/products/early_access_features/frontend/EarlyAccessFeature.js 10.9 KiB 🟢 -1.5 KiB (-11.8%)
posthog-app/_parent/products/workflows/frontend/Broadcasts/BroadcastScene.js 83.0 KiB 🔺 +1.4 KiB (+1.8%)
posthog-app/_parent/products/error_tracking/frontend/scenes/ErrorTrackingIssueScene/ErrorTrackingIssueScene.js 94.3 KiB 🔺 +1.4 KiB (+1.5%)
posthog-app/_parent/products/logs/frontend/LogsScene.js 47.5 KiB 🟢 -1.3 KiB (-2.7%)
posthog-app/_parent/products/workflows/frontend/Broadcasts/BroadcastsScene.js 14.1 KiB 🟢 -1.3 KiB (-8.6%)
posthog-app/_parent/products/actions/frontend/pages/Action.js 11.3 KiB 🟢 -1.3 KiB (-10.5%)
posthog-app/_parent/products/ai_observability/frontend/AIObservabilityScene.js 167.2 KiB 🟢 -1.3 KiB (-0.8%)
posthog-app/_parent/products/persons/frontend/pages/PersonScene.js 45.2 KiB 🔺 +1.3 KiB (+2.9%)
posthog-app/src/scenes/notebooks/NotebookCanvasScene.js 25.4 KiB 🔺 +1.3 KiB (+5.2%)
posthog-app/src/scenes/notebooks/NotebookPanel/NotebookPanel.js 27.1 KiB 🔺 +1.3 KiB (+4.9%)
posthog-app/_parent/products/groups/frontend/pages/Group.js 35.7 KiB 🔺 +1.3 KiB (+3.7%)
posthog-app/_parent/products/workflows/frontend/WorkflowsScene.js 25.5 KiB 🟢 -1.2 KiB (-4.6%)
posthog-app/_parent/products/customer_analytics/frontend/scenes/CustomerAnalyticsAccountScene/CustomerAnalyticsAccountScene.js 56.5 KiB 🟢 -1.2 KiB (-2.1%)
exporter/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.60_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/dist.js 1.2 KiB 🔺 +1.2 KiB (new)
posthog-app/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.60_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/dist.js 1.2 KiB 🔺 +1.2 KiB (new)
posthog-app/_parent/products/ai_observability/frontend/prompts/LLMPromptScene.js 52.5 KiB 🟢 -1.2 KiB (-2.2%)
posthog-app/src/scenes/data-management/definition/DefinitionView.js 36.0 KiB 🟢 -1.2 KiB (-3.2%)
posthog-app/src/scenes/experiments/Experiment.js 295.7 KiB 🔺 +1.2 KiB (+0.4%)
toolbar/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.60_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/chunk-dist.js 1.2 KiB 🔺 +1.2 KiB (new)
posthog-app/src/scenes/hog-functions/HogFunctionScene.js 55.6 KiB 🟢 -1.2 KiB (-2.1%)
posthog-app/src/lib/hog.js 1.1 KiB 🔺 +1.1 KiB (new)
posthog-app/src/scenes/session-recordings/kiosk/SessionRecordingsKiosk.js 24.5 KiB 🟢 -1.1 KiB (-4.4%)
posthog-app/src/scenes/session-recordings/player/modal/SessionPlayerModal.js 17.1 KiB 🟢 -1.1 KiB (-6.2%)
posthog-app/_parent/products/replay_vision/frontend/observations/ObservationRecording.js 16.2 KiB 🟢 -1.1 KiB (-6.5%)
posthog-app/src/scenes/session-recordings/detail/SessionRecordingDetail.js 16.5 KiB 🟢 -1.1 KiB (-6.4%)
exporter/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.57_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/dist.js removed 🟢 -1.1 KiB (-100.0%)
posthog-app/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.57_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/dist.js removed 🟢 -1.1 KiB (-100.0%)
posthog-app/src/scenes/session-recordings/file-playback/SessionRecordingFilePlaybackScene.js 19.2 KiB 🟢 -1.1 KiB (-5.5%)
toolbar/_parent/node_modules/.pnpm/@posthog+hedgehog-mode@0.0.57_prop-types@15.8.1_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/hedgehog-mode/dist/chunk-dist.js removed 🟢 -1.1 KiB (-100.0%)
posthog-app/src/scenes/sessions/SessionProfileScene.js 29.4 KiB 🟢 -1.1 KiB (-3.6%)
posthog-app/_parent/products/ai_observability/frontend/AIObservabilityTraceScene.js 178.2 KiB 🟢 -1.1 KiB (-0.6%)
posthog-app/_parent/products/ai_observability/frontend/evaluations/AIObservabilityEvaluation.js 88.5 KiB 🔺 +1.1 KiB (+1.2%)
posthog-app/src/scenes/session-recordings/playlist/SessionRecordingsPlaylistScene.js 21.5 KiB 🟢 -1.1 KiB (-4.7%)
posthog-app/src/scenes/insights/InsightScene.js 49.0 KiB 🟢 -1.0 KiB (-2.1%)
posthog-app/src/lib/components/TerraformExporter/TerraformExportModal.js 1.0 KiB 🔺 +1.0 KiB (new)
posthog-app/src/scenes/activity/explore/EventsScene.js 12.9 KiB 🟢 -1.0 KiB (-7.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.63 MiB · 22 files 🔺 +22.3 KiB (+1.4%) █████████░ 88.7% 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.72 MiB · 661 files 🔺 +159.2 KiB (+4.4%) █████████░ 92.3% of 4.03 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
7.59 MiB · 2,409 files 🟢 -12.6 KiB (-0.2%) █████████░ 91.0% of 8.34 MiB
dashboard scene
src/scenes/dashboard/Dashboard.tsx
9.67 MiB · 3,392 files (no base measurement) ███████░░░ 71.8% of 13.48 MiB
today home path
src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
7.61 MiB · 2,417 files (no base measurement) █████████░ 88.6% of 8.58 MiB
events scene
src/scenes/activity/explore/EventsScene.tsx
9.29 MiB · 3,244 files (no base measurement) ███████░░░ 73.5% of 12.64 MiB
replay detail scene
src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
12.12 MiB · 4,130 files (no base measurement) ████████░░ 77.1% of 15.72 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
🟢 src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 src/scenes/project-homepage/today/TodayReportPage.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.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
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
220.3 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
92.7 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
29.0 KiB ../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/schemas.js
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 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
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
92.7 KiB src/products.tsx
90.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
Largest files eagerly shipped from src/scenes/dashboard/Dashboard.tsx
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 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
181.8 KiB src/queries/validators.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
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
92.7 KiB src/products.tsx
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 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
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
92.7 KiB src/products.tsx
90.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
Largest files eagerly shipped from src/scenes/activity/explore/EventsScene.tsx
Size File
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 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
181.8 KiB src/queries/validators.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
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
92.7 KiB src/products.tsx
Largest files eagerly shipped from src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
Size File
315.5 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/rrweb.js
306.2 KiB ../node_modules/.pnpm/posthog-js@1.435.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
279.9 KiB src/taxonomy/core-filter-definitions-by-group.json
220.3 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
181.8 KiB src/queries/validators.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
110.1 KiB ../packages/quill/packages/quill/dist/index.js
100.5 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js

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.20 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.20 MiB · 19 files 🔺 +11.3 KiB (+0.5%) ████░░░░░░ 38.4% of 5.72 MiB
Deferred (lazy) 2.11 MiB · 44 files 🔺 +9.6 KiB (+0.4%) 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
835.6 KiB dist/toolbar/toolbar-app-IDXKA4WE.css
657.5 KiB dist/toolbar/chunk-chunk-BALLB66W.js
259.4 KiB dist/toolbar/chunk-chunk-7JWMBALG.js
138.2 KiB dist/toolbar/chunk-chunk-RXJG6CZC.js
131.8 KiB dist/toolbar/chunk-chunk-FDH2IBXT.js
75.2 KiB dist/toolbar/toolbar-app-YIYWW6XJ.js
69.0 KiB dist/toolbar/chunk-chunk-TSAL54PB.js
35.6 KiB dist/toolbar/chunk-chunk-MM7MZI2L.js
21.0 KiB dist/toolbar/chunk-chunk-EZFR5QGQ.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.89 MiB (-0.2%)

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

Total: 960.45 MiB · 🟢 -1.89 MiB (-0.2%)

✅ Playwright — all passed

All tests passed.

View test results →

@greptile-apps

greptile-apps Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Retrigger

[Medium risk] Fixes feature flag display and selection in UI components.

The PR does not appear safe to merge until recent flag fetches cannot overwrite later survey selections and flag popover titles consistently use the key.

Reviews (2) · Last reviewed commit: "fix(flags): stop passing a recent flag s..."

// PropertyKeyInfo resolves the label from it.
<PropertyKeyInfo
value={item.name ?? ''}
value={item.name || group.getName?.(item) || ''}

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 Flag title shows description
When a feature flag has a nonempty description, item.name wins before group.getName, so the hover panel shows the description as its title while the Recent row shows the key. This makes the flag harder to identify consistently. Prefer the key-based group label for feature flags, while preserving raw keys for property groups.

Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/src/lib/components/DefinitionPopover/DefinitionPopoverContents.tsx
Line: 764

Comment:
**Flag title shows description**
When a feature flag has a nonempty description, `item.name` wins before `group.getName`, so the hover panel shows the description as its title while the Recent row shows the key. This makes the flag harder to identify consistently. Prefer the key-based group label for feature flags, while preserving raw keys for property groups.

---

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

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@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.

📝 Walkthrough

Walkthrough

Recent-item records now retain label fields used by feature flags, notebooks, groups, and standard items. Feature-flag recents without a key are filtered out. Local-list and popover labels use group-specific fallback values. FlagSelector resolves feature-flag IDs and keys, and survey editing stores a supplied or fetched flag object.

Priority: ➖ Normal

Merge Risk: 🟡 Moderate · up to 36dc6

Picking a flag from Recent in the survey editor now loads the flag in the background. If the user changes or clears the selection before that load finishes, the survey can end up linked to the wrong flag or to no flag. Guard the late result before merging.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 36dc6

The change remains within existing frontend selection flows and does not demonstrate a new permission bypass. However, a delayed flag lookup can overwrite or clear a newer survey selection, potentially leaving targeting configuration inconsistent. Server-side enforcement and final save behavior were not fully verified.

Retained concerns

  • Medium · reliability · inferred: The new Recent-selection lookup is not bound to the current survey selection. After a later selection or clear operation, an older successful response can restore mismatched flag configuration, and an older failed response can clear the newer linked flag ID. Variant controls consume that configuration, so request failure or reordering is not contained to the selection that initiated it. This can affect eventual targeting if the resulting state is saved; final serialization was not verified.
Security review details

Security Blast Radius

  • inferred — The demonstrated transition problem affects the survey currently being edited and its linked-flag and variant state. Shared selector identity also reaches replay and ingestion configuration consumers, but the inspected change does not establish cross-project access or additional privileges.

Trust Boundaries and Controls

  • observed — Browser-cached identity enters the same shared selection callback used previously. The ingestion selector retains project-admin and loading/read-only UI restrictions and discards the optional full object. These frontend controls do not establish server-side authorization or submitted ID/key ownership validation, which remain outside verified coverage.

Resilience and Maintainability Implications

  • inferred — The added lookup expands a pre-existing asynchronous ordering weakness. Selection resets variant conditions and clearing removes the current object, but neither action invalidates pending lookups. Consequently, completion of an older request can undo newer state changes rather than remaining isolated to its initiating selection.

Hardening Proposals

  • proposed — Bind hydration completion to a selection generation and current flag ID, invalidate it on clear or replacement, and keep ID, configuration and variant conditions consistent across success and failure. A failed obsolete request should not clear a newer selection.
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description is complete and explains the problem, user-visible changes, screenshots, test rationale, executed checks, release status, docs impact, and agent context. It does not record the CodeRab…
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • 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.

@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.

🧹 Nitpick comments (1)
frontend/src/lib/components/DefinitionPopover/DefinitionPopoverContents.tsx (1)

759-764: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a FeatureFlags popover header regression test.

The DefinitionPopover tests and stories do not render a FeatureFlags item. A test elsewhere confirms that { name: '', key: 'checkout-redesign' } displays the key in a recent-filter row, but it does not exercise ControlledDefinitionPopover.

A regression that removes the group.getName?.(item) fallback can therefore leave the popover title empty without a test failure. Add a focused popover assertion for a FeatureFlags item with an empty or absent name and a present key.


ℹ️ Review info
⚙️ Run configuration

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

Review profile: QUIET

Plan: Enterprise

Run ID: 56c9abc2-dbba-4c89-b087-234b426ed2d7

📥 Commits

Reviewing files that changed from the base of the PR and between b786130 and a91475f.

📒 Files selected for processing (11)
  • frontend/src/lib/components/DefinitionPopover/DefinitionPopoverContents.tsx
  • frontend/src/lib/components/FlagSelector.test.tsx
  • frontend/src/lib/components/FlagSelector.tsx
  • frontend/src/lib/components/TaxonomicFilter/InfiniteList.tsx
  • frontend/src/lib/components/TaxonomicFilter/TaxonomicFilterKeyOnly.test.tsx
  • frontend/src/lib/components/TaxonomicFilter/hooks/useTaxonomicFilter.ts
  • frontend/src/lib/components/TaxonomicFilter/recentTaxonomicFiltersLogic.test.ts
  • frontend/src/lib/components/TaxonomicFilter/recentTaxonomicFiltersLogic.ts
  • frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx
  • frontend/src/lib/components/TaxonomicFilter/utils/suggestedContextFilters.test.ts
  • frontend/src/lib/components/TaxonomicFilter/utils/suggestedContextFilters.ts

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

A saved replay filter labels itself with `derived_name` when it has no name, and a group labels itself with `group_key`. Both fields were dropped when a pick was stored as a recent, so those rows read "Unnamed" or stayed blank for the same reason a feature flag did.

Generated-By: PostHog Desktop
Task-Id: 1b898a61-0065-4b4d-b7b2-84f843040805
@trunk-io

trunk-io Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Static Badge   Static Badge   Static Badge

Failed Test Failure Summary Logs
Scenes/Code review Default play-test The test failed because a logic component was not mounted when accessed, and there were unhandled network requests intercepted by the mock service... Logs ↗︎
Scenes-App/SidePanels SidePanelNotebooks smoke-test The test timed out while waiting for loading indicators or spinners to disappear. Logs ↗︎

View Full Report ↗︎ ⋅ Docs

The flag picker handed callers whatever row was clicked. A row from the Feature Flags list is the flag; a row from the Recent category is a stored summary that holds only what labels it. Callers read the rest off that object, so a pick from Recent produced a broken link rather than a visibly empty one.

A replay trigger persists `{id, key}` to the team and the key was undefined, so the trigger matched nothing. The survey editor stored the summary as `linked_flag`, leaving the variant selector without a flag to read.

`pickedFeatureFlag` now resolves the id (falling back to the value the picker keys the row by), requires a key, and returns the flag only when the row really is one. The third `onChange` argument is optional, so a caller that needs the whole flag loads it by id, which the product tour field already did and the survey editor now does too.

Generated-By: PostHog Desktop
Task-Id: 1b898a61-0065-4b4d-b7b2-84f843040805

@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

🧹 Nitpick comments (1)
frontend/src/lib/components/FlagSelector.test.tsx (1)

45-45: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the change-history comment.

Describe the invariant this test protects: a Recent summary has no flag configuration and must not be returned as flag. Remove the account of what the previous behavior let callers store. As per coding guidelines, “Never record how the code got here.”

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

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

Review profile: QUIET

Plan: Enterprise

Run ID: 4fa7e98f-c784-4c73-ab57-27dabf0bd880

📥 Commits

Reviewing files that changed from the base of the PR and between ee5620d and 36dc61c.

📒 Files selected for processing (3)
  • frontend/src/lib/components/FlagSelector.test.tsx
  • frontend/src/lib/components/FlagSelector.tsx
  • frontend/src/scenes/surveys/SurveyEdit.tsx

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

Comment on lines +1468 to +1475
api.featureFlags
.get(id)
.then((linkedFlag) => {
setSurveyValue(
'linked_flag',
linkedFlag
)
})

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Ignore a fetch result after the selection changes.

If a Recent selection starts this fetch and the user selects another flag or clicks Clear before it finishes, the callback can write the first flag into the newer survey state. The failure callback can likewise clear a newer selection. Invalidate the pending request when the selection changes, and apply its result only while its ID remains selected.

@phillram phillram changed the title fix(flags): label recent feature flags with their key fix(flags): make recent feature flags show and link correctly Oct 2, 2026
@phillram
phillram marked this pull request as ready for review October 2, 2026 19:08
@phillram phillram added the reviewhog ($$$) Reviews pull requests before humans do label Oct 2, 2026
@parameterai

parameterai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Risk: No findings

The delta since the last review only refines the Recent-list filter for feature-flag entries in filterRecentsForContext: a keyless flag recent with a completed property filter (a flag dependency) is now kept in value pickers and still dropped in key-only pickers, plus matching tests. This is frontend-only UI filtering over the user's own localStorage recents; no trust boundary, auth, or injection surface is involved, and the excluded-values/operators checks still run on every kept item.

Sentinel reviewed 549f364 · Review settings

@posthog

posthog Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

🦔 PostHog Review reviewed this pull request

Found 0 must fix, 2 should fix, 1 consider.

Published 3 findings (view the review).

Resolving comments: 3/6 · 2 fixed, 1 left for you

Safe fixes are committed to the branch; every settled thread gets a reply. This line updates as threads settle.

@github-actions

github-actions Bot commented Oct 2, 2026 •

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 549f364 · build log

@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested a review from a team October 2, 2026 19:09
Comment thread frontend/src/scenes/surveys/SurveyEdit.tsx Outdated
@posthog

posthog Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

PostHog Review alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏

@posthog posthog 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.

PostHog Review

Found 2 should fix, 1 consider.

Comment thread frontend/src/scenes/surveys/SurveyEdit.tsx Outdated
Comment on lines +99 to +100
setSelectedFlag({ id: picked.id, label: picked.key })
onChange(picked.id, picked.key, picked.flag)

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.

Resolve Recent flag keys before saving replay triggers

should_fix bug

Issue description

Recent entries now store a flag key for up to 30 days. If the flag is renamed, selecting its Recent entry sends the old key through onChange. FlagTriggerSelector and triggerGroupFormLogic.addFlag persist that key without resolving the flag ID. The recording configuration forwards the stored key to SDKs unchanged. The replay trigger therefore uses the old key instead of the selected flag's current key. The live lookup updates only the button label and does not correct the saved trigger.

Why we think it's a valid issue
  • Checked: I read pickedFeatureFlag and the onChange wiring in frontend/src/lib/components/FlagSelector.tsx:39-49,92-101, and the recent-item storage in frontend/src/lib/components/TaxonomicFilter/recentTaxonomicFiltersLogic.ts. I read both replay callers: FlagTrigger/Selector.tsx and triggerGroups/triggerGroupFormLogic.ts. On the backend I read validate_session_recording_linked_flag in posthog/api/team.py, the SDK payload builder in posthog/models/remote_config.py, products/feature_flags/backend/session_recording_links.py, and the repair_replay_linked_flag_keys command.
  • Found: A recent is a snapshot of the row at pick time. It sits in localStorage per team for RECENT_FILTER_MAX_AGE_MS = 30 days (recentTaxonomicFiltersLogic.ts:19,258,287), and pickMinimalRecentItem copies key as it was then (:48-55). Nothing re-checks a flag recent against live data before selection. suggestedContextFilters.ts:35-39 only drops recents that have no key. pickedFeatureFlag then passes the stored row.key unchanged (FlagSelector.tsx:44), and onChange(picked.id, picked.key, picked.flag) sends it to the caller (:100).
  • Found: Both replay callers save that key without resolving it. FlagTriggerSelector calls onChange({ id, key, variant: null }), which writes team.session_recording_linked_flag. addFlag stores the key string as the trigger group's flag (triggerGroupFormLogic.ts:381-383). validate_session_recording_linked_flag (posthog/api/team.py:1595-1622) checks the dict shape and normalizes id, but does not look up the key from the id. remote_config.py:155-162 sends only the stored key to the SDKs as linkedFlag.
  • Found: The codebase records that a stale key is a severe failure. The session_recording_links.py:1-13 docstring says the SDKs "treat a flag they can't resolve as 'do not record', so a stale key silently turns replay off for the team". The rename rewrite relink_teams_on_key_change runs only when the rename happens, so it cannot fix a stale key written later. The repair command covers only the linked-flag column, and only when someone runs it. Its comments say it "does not read trigger groups, so a group left here stays stale".
  • Found: The UI hides the mismatch in the V1 path. flagSelectorButtonLabel ranks flagKey from the live featureFlagLogic({ id: value }) lookup above the picked label. So the button shows the flag's current key while the team row stores the old one.
  • Impact: The trigger chain is: pick flag X anywhere, rename X's key within 30 days, then pick X from Recent in replay settings. With that chain, the team's recording gate points at a key that no longer exists, and session replay stops for the whole team with no error. This PR adds this writer of stale keys. Before the PR, a Recent pick sent no key at all, so the PR improves the common case but leaves this gap. That gap contradicts the PR's claim that a flag picked from Recent now links correctly. The fix is small: when picked.flag is absent, resolve the current key by id before calling onChange. FlagSelector already loads the flag by id. The trigger needs a key rename, which is not common, but the consequence is silent, project-wide data loss in a path the PR targets on purpose. So should_fix holds.
Suggested fix

Resolve a Recent summary by ID before calling onChange. Pass the fetched flag's current key, for example onChange(currentFlag.id, currentFlag.key, currentFlag). Reuse the loaded featureFlag only when its ID matches picked.id. Refresh the stored Recent entry with the resolved key.

Prompt to fix with AI (copy-paste)
## Context
@frontend/src/lib/components/FlagSelector.tsx#L99-100

<issue_description>
Recent entries now store a flag key for up to 30 days. If the flag is renamed, selecting its Recent entry sends the old key through onChange. FlagTriggerSelector and triggerGroupFormLogic.addFlag persist that key without resolving the flag ID. The recording configuration forwards the stored key to SDKs unchanged. The replay trigger therefore uses the old key instead of the selected flag's current key. The live lookup updates only the button label and does not correct the saved trigger.
</issue_description>

<issue_validation>
- **Checked:** I read `pickedFeatureFlag` and the `onChange` wiring in `frontend/src/lib/components/FlagSelector.tsx:39-49,92-101`, and the recent-item storage in `frontend/src/lib/components/TaxonomicFilter/recentTaxonomicFiltersLogic.ts`. I read both replay callers: `FlagTrigger/Selector.tsx` and `triggerGroups/triggerGroupFormLogic.ts`. On the backend I read `validate_session_recording_linked_flag` in `posthog/api/team.py`, the SDK payload builder in `posthog/models/remote_config.py`, `products/feature_flags/backend/session_recording_links.py`, and the `repair_replay_linked_flag_keys` command.
- **Found:** A recent is a snapshot of the row at pick time. It sits in localStorage per team for `RECENT_FILTER_MAX_AGE_MS` = 30 days (`recentTaxonomicFiltersLogic.ts:19,258,287`), and `pickMinimalRecentItem` copies `key` as it was then (`:48-55`). Nothing re-checks a flag recent against live data before selection. `suggestedContextFilters.ts:35-39` only drops recents that have no key. `pickedFeatureFlag` then passes the stored `row.key` unchanged (`FlagSelector.tsx:44`), and `onChange(picked.id, picked.key, picked.flag)` sends it to the caller (`:100`).
- **Found:** Both replay callers save that key without resolving it. `FlagTriggerSelector` calls `onChange({ id, key, variant: null })`, which writes `team.session_recording_linked_flag`. `addFlag` stores the key string as the trigger group's `flag` (`triggerGroupFormLogic.ts:381-383`). `validate_session_recording_linked_flag` (`posthog/api/team.py:1595-1622`) checks the dict shape and normalizes `id`, but does not look up the key from the id. `remote_config.py:155-162` sends only the stored `key` to the SDKs as `linkedFlag`.
- **Found:** The codebase records that a stale key is a severe failure. The `session_recording_links.py:1-13` docstring says the SDKs "treat a flag they can't resolve as 'do not record', so a stale key silently turns replay off for the team". The rename rewrite `relink_teams_on_key_change` runs only when the rename happens, so it cannot fix a stale key written later. The repair command covers only the linked-flag column, and only when someone runs it. Its comments say it "does not read trigger groups, so a group left here stays stale".
- **Found:** The UI hides the mismatch in the V1 path. `flagSelectorButtonLabel` ranks `flagKey` from the live `featureFlagLogic({ id: value })` lookup above the picked label. So the button shows the flag's current key while the team row stores the old one.
- **Impact:** The trigger chain is: pick flag X anywhere, rename X's key within 30 days, then pick X from Recent in replay settings. With that chain, the team's recording gate points at a key that no longer exists, and session replay stops for the whole team with no error. This PR adds this writer of stale keys. Before the PR, a Recent pick sent no key at all, so the PR improves the common case but leaves this gap. That gap contradicts the PR's claim that a flag picked from Recent now links correctly. The fix is small: when `picked.flag` is absent, resolve the current key by id before calling `onChange`. `FlagSelector` already loads the flag by id. The trigger needs a key rename, which is not common, but the consequence is silent, project-wide data loss in a path the PR targets on purpose. So `should_fix` holds.
</issue_validation>

## Task
Investigate the issue and solve it

<potential_solution>
Resolve a Recent summary by ID before calling onChange. Pass the fetched flag's current key, for example onChange(currentFlag.id, currentFlag.key, currentFlag). Reuse the loaded featureFlag only when its ID matches picked.id. Refresh the stored Recent entry with the resolved key.
</potential_solution>

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.

Escalated: the issue is real, but the fix site is a design choice for the PR author.


  • Confirmed: a Recent flag entry keeps its key for up to 30 days, and both replay writers save that key without a lookup. The backend relinks only at rename time, so a stale key turns replay off for the team.
  • Option A: FlagSelector loads the flag by id for a Recent pick before it calls onChange. This is one change, but onChange becomes async on every surface and this PR's "callers load by id" design is reversed.
  • Option B: only the team linked flag and the trigger group addFlag load the flag by id when flag is absent, then save its current key. This keeps the PR's design.
  • Option C: refresh or drop flag Recents against live flags, so a stale key never reaches a caller.
More detail
  • Each option needs a guard so a late lookup cannot replace a newer pick. No test covers that flow now, so I did not make the change unattended.
How this was verified

No code change, so I ran no tests. I checked the code paths by reading FlagSelector.tsx, FlagTrigger/Selector.tsx, triggerGroupFormLogic.ts, posthog/api/team.py, posthog/models/remote_config.py and products/feature_flags/backend/session_recording_links.py at the current head.

@posthog posthog Bot removed the reviewhog ($$$) Reviews pull requests before humans do label Oct 2, 2026
A flag picked from Recent is loaded by id so the variant selector can read it. Any failed lookup cleared linked_flag_id, so a temporary error followed by a save removed the survey's flag targeting.

Only a 404 now ends the link, and it clears linked_flag with it. Any other failure keeps the pick and shows an error toast with a button that retries the lookup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 77ee946d-952c-4c91-95bc-424e31f3839d
@github-actions
github-actions Bot requested a deployment to preview-pr-110227 October 2, 2026 19:34 In progress
The drop for keyless flag recents also removed completed flag dependencies. Both property-filter writers store those without `key`, and their row takes its label from the property filter, so they never rendered blank.

A keyless flag recent is now dropped only when it has no completed property filter, or when the picker selects a key only and so strips that filter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 77ee946d-952c-4c91-95bc-424e31f3839d
@github-actions
github-actions Bot requested a deployment to preview-pr-110227 October 2, 2026 19:40 In progress

This branch is being deployed

1 in progress deployment
preview-pr-110227 — 549f364c Deployed Oct 2, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant