
Modifying Taxonomic Filter
- 81 installs
- 37.5k repo stars
- Updated August 5, 2026
- posthog/posthog
modifying-taxonomic-filter is a Claude Code skill for ai & agent building.
About
modifying-taxonomic-filter is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- modifying-taxonomic-filter
- AI & Agent Building
- AI-coding skill
Modifying Taxonomic Filter by the numbers
- 81 all-time installs (skills.sh)
- +6 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #5,216 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/posthog/posthog --skill modifying-taxonomic-filterAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 81 |
|---|---|
| repo stars | ★ 37.5k |
| Last updated | August 5, 2026 |
| Repository | posthog/posthog ↗ |
How do I helps with ai & agent building tasks during AI-assisted development.?
Helps with ai & agent building tasks during AI-assisted development.
Who is it for?
Best when you're working on ai & agent building and need structured help with modifying taxonomic filter.
Skip if: Teams with no ai & agent building needs, or anyone wanting a generic chat assistant without this specific workflow.
When should I use this skill?
When you need to helps with ai & agent building tasks during AI-assisted development., or when modifying-taxonomic-filter is a claude code skill for ai & agent building.
What you get
Structured output aligned to modifying-taxonomic-filter: modifying-taxonomic-filter, AI & Agent Building.
Files
Modifying the TaxonomicFilter
The 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:
1. Changes that demote items users _actually pick_ are regressions, even with all tests passing. Read "Product reality" before deciding any change is safe. Ordering, promotion, or position-0 changes need explicit human sign-off — don't let an agent decide alone. 2. There are three live variants behind two feature flags, and the rebuild is a parallel reimplementation of the legacy data + group layer — not a skin over it. A behaviour change usually has to land in both the legacy code and the rebuild, or the two arms of the experiment diverge. Read "Three variants" and "Mirroring changes" before assuming one edit is enough.
Product reality (last refreshed 2026-05-02, 90-day window)
Ratios from production telemetry. Re-run via references/refreshing-product-reality.md when older than ~3 months.
How users pick
- Top three rows carry ~80% of selections (position 0: ~56%,
position 1: ~15%, position 2: ~8%). Demoting a popular item out of the top three is a real-user regression.
- ~34% selection rate. Two of every three opens close without a
pick. p50 dwell ~7s, p90 ~53s — most opens are quick glances.
- Selection paths: ~65% via search, ~19% browsed-no-search,
~16% from recents, <1% from pinned items.
What users select
| 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% |
What users search for (share of top-8 terms)
| 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.
Empty searches
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.
Input mode
~93% typed, ~7% pasted. Both feed inputMode on taxonomic_filter_search_query.
Telemetry is a contract
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, wasStale
Legacy-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.
Three variants
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/ |
- legacy-control vs legacy-pill is the same A/B we've always had —
one codebase (taxonomicFilterLogic.tsx + InfiniteList), two render paths. Owner @pauldambra, multivariate control,pill. The direction of travel is to move everyone from control onto pill.
- rebuild-menu is a separate, opt-in experiment (
@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.
Mirroring changes across variants
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.
Pre-change checklist
- [ ] Read references when relevant: architecture,
common-pitfalls (X/Y matrix), call-sites (smoke tests), testing-patterns
- [ ] Decide whether the change must mirror across legacy and rebuild
(see "Mirroring changes") — if you can only do one, say so explicitly
- [ ] Test all three surfaces if you touched tabs/groups:
legacy-control,
legacy-pill, rebuild-menu
- [ ] Confirm shared telemetry payloads still match across both emitters
- [ ] Ordering / promotion / position-0 -> human sign-off, not agent judgement
- [ ] Flag the ongoing experiments to the human reviewer: the
control->pill rollout and the internal rebuild-menu opt-in
hogli test frontend/src/lib/components/TaxonomicFilter/TaxonomicFilter architecture
There are two architectures living side by side: the legacy kea-driven tree (below) and the rebuild (menu/ + headless/, hooks-driven). See the "Mirroring changes" table in SKILL.md for which concern lives where. This file documents both.
Legacy component tree
TaxonomicFilter
├── TaxonomicFilterSearchInput # debounced input + paste detection
├── CategoryDropdown # A/B-tested suffix picker (variant: 'pill')
└── InfiniteSelectResults
├── Tab buttons # one per visible group, hidden when empty
├── TaxonomicFilterEmptyState
└── InfiniteList # AutoSizer + react-window virtualized list
└── InfiniteListRow # item / skeleton / pinned / recentLegacy logics
| File | Owns |
|---|---|
taxonomicFilterLogic.tsx | Search query, active tab, group ordering, telemetry, keyboard nav. Spawns child list logics. |
infiniteListLogic.ts | Per-tab fetch, pagination, selection, property promotion, top-match donation, empty-result telemetry. |
recentTaxonomicFiltersLogic.ts | Recents persisted to localStorage, prefixed by team id. |
taxonomicFilterPinnedPropertiesLogic.ts | Pinned items persisted to localStorage, prefixed by team id. |
Each infiniteListLogic is keyed by taxonomicFilterLogicKey + listGroupType. Pinning/recents are shared singletons per team.
Rebuild architecture (menu/ + headless/)
Opt-in via TAXONOMIC_FILTER_MENU_REBUILD. Hooks-driven, not kea-driven — it reimplements the legacy data layer rather than wrapping it.
TaxonomicFilterMenu # menu/ — dropdown + combobox + DWH/HogQL sub-flows
└── TaxonomicFilterHeadless.Root # headless/ — Root/Input/Categories/Panel
└── useTaxonomicFilter # hooks/ — orchestrator: query, active group, ordering, selectItem
└── useGroupList (per tab) # hooks/ — fetch + pagination + min-query-length
└── useTaxonomicResource # hooks/ — resolves a group's data source
└── fetchTaxonomicListPage| File | Rebuild counterpart of |
|---|---|
utils/buildTaxonomicGroups.tsx | legacy taxonomicGroups selector |
hooks/useTaxonomicFilter.ts | legacy taxonomicGroupTypes selector + ordering |
hooks/useGroupList.ts + useTaxonomicResource.ts + fetchTaxonomicListPage.ts | legacy infiniteListLogic.ts |
hooks/useTaxonomicLocalOverrides.ts | feeds logic-backed group data the kea version got for free |
hooks/useTaxonomicGroupsContext.ts | the only kea-coupled layer of the rebuild (reads recents/pinned via bridge) |
menu/DwhFlow.tsx | legacy DWH config inlined in InfiniteList.tsx |
menu/TaxonomicFilterMenu.tsx | legacy telemetry in taxonomicFilterLogic.tsx |
headless/UX_SPEC.md is the rebuild's design source of truth — update it when locking design, then build against it.
Things that aren't where you'd guess
- Suggested-filters aggregation lives in `infiniteListLogic`, not
the parent. See topMatchesForQuery, isSuggestedFilters, results. The parent only collects matches via appendTopMatches on infiniteListResultsReceived.
- `PROMOTED_PROPERTIES_BY_SEARCH_TERM` is in `infiniteListLogic.ts`,
not in taxonomicFilterLogic.tsx.
- Pinned/recent rows carry `_pinnedContext` / `_recentContext` so
selectItem records the _original_ sourceGroupType in telemetry, not "Pinned" or "Recents".
Data flow on search
user types
-> taxonomicFilterLogic.setSearchQuery
-> debounce -> infiniteListLogic[X].setSearchQuery (per group)
-> API fetch -> loadRemoteItemsSuccess
-> empty? fire 'taxonomic filter empty result'
-> infiniteListResultsReceived(groupType, results)
-> taxonomicFilterLogic.appendTopMatches(...)
universe of matches -> infiniteListLogic[SuggestedFilters].results
(via redistributeTopMatches)redistributeTopMatches is a pure function — test in isolation. Constants: DEFAULT_SLOTS_PER_GROUP=5, MAX_TOP_MATCHES_PER_GROUP=10, SKELETON_ROWS_PER_GROUP=3. Empty groups donate slots to REDISTRIBUTION_PRIORITY_GROUPS (CustomEvents, PageviewUrls, Screens).
Selectors worth knowing
taxonomicFilterLogic: activeTab, infiniteListCounts, taxonomicGroups, topMatchItems (aggregated via appendTopMatches).
infiniteListLogic: topMatchesForQuery (per-tab donated slice), isSuggestedFilters, results (incl. skeletons, pinned, recents), showSuggestedFiltersEmptyState.
Reactive prop behavior uses propsChanged + afterMount, never kea-subscriptions — see common-pitfalls.md.
TaxonomicFilter call sites and blast radius
The TaxonomicFilter is used in dozens of places. Don't try to enumerate them — they drift. Find the current set with:
rg -l '<TaxonomicFilter\b|TaxonomicPopover|TaxonomicPropertyFilter' frontend productsWrappers (touch one, affect many)
lib/components/TaxonomicPopover/TaxonomicPopover.tsx— generic popover wrapperlib/components/PropertyFilters/components/TaxonomicPropertyFilter.tsx— property-filter rowlib/components/PropertySelect/PropertySelect.tsx— single-property selectorlib/components/EventSelect/EventSelect.tsx— single-event selectorlib/components/FlagSelector.tsx— feature-flag pickerlib/components/QuickFilters/QuickFilterForm.tsx— quick-filter authoringlib/components/IngestionControls/triggers/EventTrigger.tsx— capture trigger config
A change ripples through every consumer of these. Run their tests before shipping anything broad.
Prop combinations to think about
| Prop | Why it matters |
|---|---|
taxonomicGroupTypes | Drives which tabs appear and their order. Single-group, subset, and full-default all exist. |
excludedProperties | Hides already-selected keys; some scenes hide system-only properties this way. |
metadataSource | Drives whether the property panel queries events / persons / sessions / warehouse. |
eventNames | Insight-series names so per-event property promotion can run; reactive via propsChanged. |
onChange / onEnter | Both shapes exist; onEnter is used for HogQL expression entry without a concrete pick. |
optionsFromProp | Some pickers inject local items instead of fetching from the API. |
Smoke test before shipping a broad change
- [ ] Add an event filter inside an insight (Trends or Funnel)
- [ ] Add a property breakdown to a Trends insight
- [ ] Add a person property filter on the Persons scene
- [ ] Add a filter inside Replay's universal filter bar
- [ ] Add a cohort field condition
- [ ] Open the property selector inside Web analytics conversion goal
- [ ] Check all three surfaces (
legacy-control,legacy-pill,rebuild-menu) if you touched tab rendering. Reach the rebuild via a call site that goes throughTaxonomicPopoverorTaxonomicPropertyFilterwithTAXONOMIC_FILTER_MENU_REBUILDon — call sites with their own popover (e.g.ActionFilterRow) never render it
Common pitfalls when modifying TaxonomicFilter
Generic engineering rules don't earn space here. This is the component-specific traps.
"If you change X, also check Y"
| Change | Verify |
|---|---|
| Tab ordering or visibility | Keyboard nav (Tab cycles), default active tab, all three surfaces (legacy-control, legacy-pill, rebuild-menu), suggested-filters tab; mirror the change in buildTaxonomicGroups.tsx + useTaxonomicFilter.ts |
PROMOTED_PROPERTIES_BY_SEARCH_TERM or sort order | $email and $current_url still at position 0 for email / url searches |
Selectors feeding topMatchesForQuery | Per-tab output and parent appendTopMatches aggregation; SuggestedFilters tab still populates |
| Search input or paste handling | inputMode field on taxonomic_filter_search_query still distinguishes typed/pasted/mixed |
selectItem logic | Telemetry payload (sourceGroupType, wasFromRecents, wasFromPinnedList, wasQuickFilter, position); recents still recorded; onChange still fires |
| Persistence keys (recents or pinned) | Team-id prefix still applied; items don't leak across teams |
| Filter open/close lifecycle | cache.openedAt / cache.hadSelection still set; taxonomic filter closed still fires with dwellMs and hadSelection |
Adding a TaxonomicFilterGroupType | Group config in taxonomicFilterLogic.tsx, shortcut routing, telemetry includes the new type, every consumer's taxonomicGroupTypes prop updated |
| Reactive prop behavior in any logic | Use propsChanged + afterMount, not subscriptions (see below) |
Suggested-filters items appear in API-response order; don't assert on order within that tab unless you control the timing.
propsChanged + afterMount, not subscriptions
afterMount(({ actions, props }) => {
if (props.eventNames?.length) actions.ensureLoadedForEvents(props.eventNames)
}),
propsChanged(({ actions, props }, oldProps) => {
if (props.eventNames !== oldProps.eventNames && props.eventNames?.length) {
actions.ensureLoadedForEvents(props.eventNames)
}
}),kea-subscriptions are slower and have re-mount cost. Established by perf(taxonomic-filter): replace eventNames subscription with propsChanged + afterMount.
Refreshing the Product reality section
Re-run these every ~3 months. Public OSS repo: convert to ratios before writing back — never commit absolute event or user counts. All queries use a 90-day window; keep that consistent so trends are comparable.
1. Selection breakdown by source group type
Computes share of all taxonomic filter item selected events grouped by sourceGroupType. Drives the "What users select" table.
SELECT
properties.sourceGroupType AS source_group_type,
count() AS selections,
round(100 * count() / (
SELECT count()
FROM events
WHERE event = 'taxonomic filter item selected'
AND timestamp >= now() - INTERVAL 90 DAY
), 1) AS share_pct
FROM events
WHERE event = 'taxonomic filter item selected'
AND timestamp >= now() - INTERVAL 90 DAY
GROUP BY source_group_type
ORDER BY selections DESC
LIMIT 302. Top searches (share of top-N)
Computes the share of each top-N search term among the top 8 only. Avoids exposing absolute search volume.
WITH top_terms AS (
SELECT lower(trim(toString(properties.searchQuery))) AS q, count() AS searches
FROM events
WHERE event = 'taxonomic_filter_search_query'
AND timestamp >= now() - INTERVAL 90 DAY
AND length(toString(properties.searchQuery)) > 0
GROUP BY q
ORDER BY searches DESC
LIMIT 8
)
SELECT q, round(100 * searches / sum(searches) OVER (), 1) AS share_pct
FROM top_terms
ORDER BY share_pct DESC3. Top empty-result searches
Reveals taxonomy gaps. Use the qualitative findings (which terms are empty in which groups), not absolute counts.
SELECT
lower(trim(toString(properties.searchQuery))) AS q,
properties.groupType AS group_type,
count() AS empties
FROM events
WHERE event = 'taxonomic filter empty result'
AND timestamp >= now() - INTERVAL 90 DAY
GROUP BY q, group_type
ORDER BY empties DESC
LIMIT 404. Selection rate and dwell distribution
Drives the "About one in three opens results in a selection" line, plus the dwell-time bullet.
SELECT
round(100 * countIf(toBool(properties.hadSelection)) / count(), 1) AS selection_rate_pct,
quantile(0.5)(toFloat(properties.dwellMs)) AS p50_dwell_ms,
quantile(0.9)(toFloat(properties.dwellMs)) AS p90_dwell_ms
FROM events
WHERE event = 'taxonomic filter closed'
AND timestamp >= now() - INTERVAL 90 DAY5. Selection-source mix
Drives the "65% involve search / 19% browsed / 16% from recents / <1% from pinned" line. Use only the relative shares — drop the absolute counts before writing back.
SELECT
round(100 * countIf(toBool(properties.hadSearchInput)) / count(), 1) AS had_search_pct,
round(100 * countIf(NOT toBool(properties.hadSearchInput) AND NOT toBool(properties.wasFromRecents) AND NOT toBool(properties.wasFromPinnedList)) / count(), 1) AS browsed_no_search_pct,
round(100 * countIf(toBool(properties.wasFromRecents)) / count(), 1) AS from_recents_pct,
round(100 * countIf(toBool(properties.wasFromPinnedList)) / count(), 1) AS from_pinned_pct
FROM events
WHERE event = 'taxonomic filter item selected'
AND timestamp >= now() - INTERVAL 90 DAY6. Position distribution
Drives the "first three rows carry ~80% of selections" line.
WITH positions AS (
SELECT toInt(properties.position) AS pos
FROM events
WHERE event = 'taxonomic filter item selected'
AND timestamp >= now() - INTERVAL 90 DAY
AND properties.position IS NOT NULL
)
SELECT
pos,
round(100 * count() / (SELECT count() FROM positions), 1) AS share_pct
FROM positions
GROUP BY pos
ORDER BY pos ASC
LIMIT 107. Input mode mix
Drives the "~93% typed / ~7% pasted" line.
SELECT
properties.inputMode AS input_mode,
round(100 * count() / (
SELECT count()
FROM events
WHERE event = 'taxonomic_filter_search_query'
AND timestamp >= now() - INTERVAL 90 DAY
AND properties.inputMode IS NOT NULL
), 1) AS share_pct
FROM events
WHERE event = 'taxonomic_filter_search_query'
AND timestamp >= now() - INTERVAL 90 DAY
AND properties.inputMode IS NOT NULL
GROUP BY input_mode
ORDER BY share_pct DESCUpdating the doc
1. Run each query above via posthog:execute-sql against project 2 on us.posthog.com. 2. Convert findings into ratios (drop absolute event and user counts). 3. Update the tables in SKILL.md and bump the "last refreshed" date. 4. If the qualitative story changed (e.g. email is no longer dominant, or pinned-items usage grew significantly), revise the prose above the tables — not just the numbers. Big changes deserve a heads-up to whoever owns the component, since they may affect ongoing roadmap decisions.
Testing patterns for TaxonomicFilter
For boilerplate, copy from existing tests in frontend/src/lib/components/TaxonomicFilter/. This doc only covers the things that aren't obvious until they bite.
Required setup (or tests fail silently)
AutoSizer mock — without this the virtualized list renders at zero height and getByTestId('prop-filter-…') returns nothing while tests appear to pass.
jest.mock('lib/components/AutoSizer', () => ({
AutoSizer: ({ renderProp }: { renderProp: (info: { height: number; width: number }) => JSX.Element }) =>
renderProp({ height: 400, width: 400 }),
}))Search-aware mock handlers — a static handler hides search-filtering bugs. Always read the search param:
'/api/projects/:team_id/event_definitions': (req) => {
const search = req.url.searchParams.get('search')
const filtered = search ? mockEventDefinitions.filter(e => e.name.includes(search)) : mockEventDefinitions
return [200, { results: filtered, count: filtered.length }]
}Persons properties — match what the frontend actually calls. Today persons URLs are constructed against /api/environments/:team_id/persons/properties in the calling kea logic, so mock at that path. (The backend exposes both /api/projects/ and /api/environments/ for persons; /api/projects/ is the canonical path but the persons frontend hasn't been migrated.)
Mount shared models in beforeEach:
beforeEach(() => {
initKeaTests()
actionsModel.mount()
groupsModel.mount()
})Test IDs
| Element | Pattern |
|---|---|
| Search input | taxonomic-filter-searchfield |
| Tab buttons | taxonomic-tab-{groupType} |
| List items | prop-filter-{type}-{index} |
Active tab assertion: expect(tab).toHaveClass('LemonTag--primary').
Logic-level integration tests
taxonomicFilterLogic spawns child infiniteListLogic per group type — mount them all, then await loadRemoteItemsSuccess before asserting:
const logic = taxonomicFilterLogic({ taxonomicFilterLogicKey: 'test', taxonomicGroupTypes, onChange: jest.fn() })
logic.mount()
for (const groupType of taxonomicGroupTypes) {
infiniteListLogic({ ...logic.props, listGroupType: groupType }).mount()
}
for (const groupType of taxonomicGroupTypes) {
await expectLogic(infiniteListLogic({ ...logic.props, listGroupType: groupType })).toDispatchActions([
'loadRemoteItemsSuccess',
])
}Don't delete the property-promotion test
email and url searches must promote $email and $current_url to position 0 (see Product reality in SKILL.md):
await userEvent.type(searchField, 'url')
await waitFor(() => expect(screen.getByText('$current_url')).toBeInTheDocument())redistributeTopMatches is a pure function — test in isolation with parameterized cases.
Related skills
FAQ
What does modifying-taxonomic-filter do?
modifying-taxonomic-filter is a Claude Code skill for ai & agent building.
When should I use modifying-taxonomic-filter?
When you need to helps with ai & agent building tasks during AI-assisted development., or when modifying-taxonomic-filter is a claude code skill for ai & agent building.
What are the main capabilities?
modifying-taxonomic-filter; AI & Agent Building; AI-coding skill.