Guides safe modification of the TaxonomicFilter — PostHog's multi-tab picker for events, actions, properties, cohorts, and more. Front-loads the empirical product reality (what users actually pick and search for) plus the three live variants (legacy-control, legacy-pill behind TAXONOMIC_FILTER_CATEGORY_DROPDOWN, and the opt-in rebuild menu behind TAXONOMIC_FILTER_MENU_REBUILD) so changes are judged against real behavior and mirrored across surfaces, not made against one arm in isolation. Use when adding features, fixing bugs, or refactoring TaxonomicFilter, the rebuild menu, or the headless filter panel.
64
76%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Fix and improve this skill with Tessl
tessl review fix ./.agents/skills/modifying-taxonomic-filter/SKILL.mdThe TaxonomicFilter is the picker users hit to choose any "thing PostHog
knows about" — events, properties, actions, cohorts, groups. It's the
on-ramp into almost every analytics and replay configuration. Code lives
in frontend/src/lib/components/TaxonomicFilter/.
Two unbreakable rules:
Ratios from production telemetry. Re-run via references/refreshing-product-reality.md when older than ~3 months.
| Source group type | Share |
|---|---|
events | ~40% |
event_properties | ~30% |
person_properties | ~14% |
cohorts | ~2% |
email_addresses | ~2% |
actions | ~2% |
pageview_urls | ~1% |
| everything else | <1% |
| Term | Share |
|---|---|
email | ~29% |
url | ~22% |
user | ~12% |
utm | ~10% |
page | ~9% |
path | ~8% |
current | ~6% |
country | ~5% |
email and url are over half the top-8. They're the entire reason
PROMOTED_PROPERTIES_BY_SEARCH_TERM (in infiniteListLogic.ts) maps
them to $email and $current_url at position 0. Touching
promotion or ordering needs explicit human sign-off.
email, url, utm, path against cohorts, event_feature_flags,
session_properties produce most empty-result events — users type the
same canonical terms across every tab. Tab order, suggested-filters
aggregation, and shortcut routing are how they get to the right answer.
~93% typed, ~7% pasted. Both feed inputMode on taxonomic_filter_search_query.
Treat property shapes as a public API. Every taxonomic filter * event now
carries a surface property (legacy-control / legacy-pill /
rebuild-menu) so the experiment arms are distinguishable by an explicit
property, not a feature-flag join. The legacy stamp comes from
legacyTaxonomicSurface() in taxonomicFilterSurface.ts; the rebuild stamps
rebuild-menu from menu/TaxonomicFilterMenu.tsx.
Shared events both surfaces emit (keep these comparable across arms):
taxonomic filter closed — surface, dwellMs, hadSelection (legacy
also sends groupType; the rebuild omits it — there's no single active
tab at close)taxonomic filter item selected — surface, groupType,
sourceGroupType, wasFromRecents, wasFromPinnedList, wasQuickFilter,
hadSearchInput, position, query, wasStaleLegacy-only: taxonomic_filter_search_query
(searchQuery, groupType, inputMode, pastedFraction),
taxonomic filter empty result (groupType, searchQuery),
taxonomic filter include stale toggled,
taxonomic filter category dropdown opened (pill only).
Rebuild-only menu events: taxonomic filter menu opened / drilled /
closed / option clicked / item selected.
When you add a property to a shared event, add it to both emitters or the arms stop being comparable. Adding properties: fine. Removing dead ones: fine. Renaming or repurposing silently is the worst case — dashboards keep working and start lying.
Two feature flags, three surfaces. A bug report that doesn't reproduce locally is almost always a variant mismatch — confirm which surface the reporter is on first.
| Surface | Flag | Value | What renders |
|---|---|---|---|
legacy-control | TAXONOMIC_FILTER_CATEGORY_DROPDOWN | 'control' | original tab-pill UI |
legacy-pill | TAXONOMIC_FILTER_CATEGORY_DROPDOWN | 'pill' | suffix category dropdown (CategoryDropdown.tsx) |
rebuild-menu | TAXONOMIC_FILTER_MENU_REBUILD | on | ground-up rewrite in menu/ over headless/ |
taxonomicFilterLogic.tsx + InfiniteList), two render
paths. Owner @pauldambra, multivariate control,pill. The direction
of travel is to move everyone from control onto pill.@adamleith) being
tested internally. It is a fresh implementation: the menu/ dropdown
and combobox UI on top of headless/ (a hooks-based filter panel). It
does not route through taxonomicFilterLogic/infiniteListLogic;
it has its own group definitions, fetch/pagination, and ordering. See
headless/UX_SPEC.md for its design source of truth.The rebuild is opt-in in exactly two consumer wrappers:
TaxonomicPopover.tsx and
PropertyFilters/components/TaxonomicPropertyFilter.tsx. Both check
TAXONOMIC_FILTER_MENU_REBUILD and render <TaxonomicFilterMenu> or the
legacy <TaxonomicFilter>. Call sites that build their own popover (e.g.
ActionFilterRow) never see the rebuild — so "does this reach the
rebuild?" depends on the call site, not a single global switch.
Touching tab/group rendering means testing all three surfaces.
The rebuild reimplements the legacy data layer rather than reusing it, so the same concern lives in two files. There is no lint rule or test enforcing parity — the only guard is "Mirrors the legacy…" comments. When you change one, change the other (or flag to the human that you can't).
| Concern | Legacy | Rebuild |
|---|---|---|
| Group definitions (endpoint, excluded props, group meta) | taxonomicFilterLogic.tsx taxonomicGroups selector | utils/buildTaxonomicGroups.tsx |
| Group ordering + SuggestedFilters injection | taxonomicFilterLogic.tsx taxonomicGroupTypes selector | hooks/useTaxonomicFilter.ts resolveTaxonomicGroupTypes |
| Per-tab fetch / pagination / min-query-length | infiniteListLogic.ts | hooks/useGroupList.ts + useTaxonomicResource.ts + fetchTaxonomicListPage.ts |
| Data-warehouse config flow | inline in InfiniteList.tsx | menu/DwhFlow.tsx |
taxonomic filter item selected / closed telemetry | taxonomicFilterLogic.tsx | menu/TaxonomicFilterMenu.tsx |
New TaxonomicFilterGroupType enum value | types.ts (shared) — then add group config in both tables above | |
| Logic-backed group data (Actions, Dashboards, …) | already in kea | also register in hooks/useTaxonomicLocalOverrides.ts |
Genuinely shared — change once: types.ts (the enum),
utils/promoteProperties.ts (PROMOTED_PROPERTIES_BY_SEARCH_TERM),
utils/redistributeTopMatches.ts, recentTaxonomicFiltersLogic.ts and
taxonomicFilterPinnedPropertiesLogic.ts (the rebuild reads recents/pinned
through these via a bridge, it doesn't fork them).
One intentional divergence is already documented in
useTaxonomicFilter.ts: the rebuild always leads with SuggestedFilters,
whereas legacy gates that on the pill variant. Preserve documented
divergences; don't "fix" them into parity.
legacy-control,
legacy-pill, rebuild-menurebuild-menu opt-inhogli test frontend/src/lib/components/TaxonomicFilter/6fca5f8
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.