You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Implements items #1 and #2 from #304 (catalog-search component coverage gaps) and adopts the dominant plasmicpkgs cloneElement injection pattern (henceforth "Pattern C") for catalog-search components. Pagination retro is split into a separate follow-up — see Out of Scope.
Problem Statement
Two affected user types:
Storefront shoppers browsing the catalog-search results page can't:
Clear all active filters in one click — they must un-toggle each facet pill individually.
See at a glance which filters are active above the grid (no chip row like "Brand: Leather × · Price ≥ $25 ×").
Designers in Plasmic Studio building those storefronts can't drop in primitives for either UX, because EPClearRefinements and EPCurrentRefinements don't exist yet (see #304 items #1 and #2).
There is also a related developer-experience problem: the existing catalog-search components (EPSearchPagination, EPRefinementList, etc.) ship editable default chrome but require designers to manually wire onClick interactions in Studio for every action — clear, refine, paginate. Meanwhile, the dominant pattern across plasmicpkgs (used by AddToCartButton, plasmic-cms, antd5, react-aria, dnd-kit, keen-slider, plasmic-tabs — 12+ call sites across 7 packages) is defaultValue + React.cloneElement to inject behavioral props at render time while keeping the default chrome editable. The catalog-search package hasn't adopted this pattern. The two new components are a natural place to introduce it.
Extract a shared cloneWithInjectedHandlers helper as a deep module — small, isolated, reusable across the 5+ components that will adopt the pattern.
Pre-wire onClick (and disabled where applicable) into the default slot, so designers drop the component and it works end-to-end without any Studio interaction wiring. Designers customizing the default chrome (or supplying their own onClick) compose cleanly: the designer's handler runs first, then the injected one.
As a shopper who has applied multiple facet filters, I want a single Clear all action so I can reset the search without un-toggling each filter individually.
As a shopper, I want the Clear all control to be visibly disabled when no filters are active, so I'm not misled into thinking it will do something.
As a shopper, I want a chip row above the results showing each active filter (e.g. "Brand: Leather"), so I can see at a glance what's narrowing my view.
As a shopper, I want each chip to dismiss its specific refinement when I click it, so I can drop one filter without affecting the others.
As a shopper, I want the chip row to disappear entirely when no filters are active, so the layout doesn't leave an empty band above the grid.
As a shopper using a price range, I want my range to display readably (e.g. "≥ $25"), not as a raw value.
As a shopper using hierarchical category filters, I want the chip to reflect the full path (e.g. "Bags > Leather"), so I know exactly what's selected.
Designers in Plasmic Studio
As a designer, I want to drop EPClearRefinements onto a search page and see a working Clear all button on the canvas immediately, without going to Interactions to wire onClick.
As a designer, I want to rename the default "Clear all" text to "Reset filters" (or any localized equivalent) without losing the wired behavior.
As a designer, I want to replace the default button with my own custom button or styled element, and have the clear action still trigger when the shopper clicks it.
As a designer building a custom layout (e.g. clear control inside a popover), I want to wire onClick to $ctx.clearRefinementsData.clear myself and have it work even when my structure breaks the auto-injection.
As a designer, I want to drop EPCurrentRefinements and see a styled chip on the canvas immediately, repeating per active refinement, with dismiss-on-click already wired.
As a designer, I want to bind chip text to the per-item context ($ctx.currentRefinementChip.attributeLabel, .label, etc.) so I can format chips like "Brand: Leather".
As a designer, I want to know the refinement type (facet, numeric, hierarchical, etc.) per chip, so I can conditionally style range chips differently from facet chips.
As a designer, I want to limit which attributes appear in clear or chip components via includedAttributes / excludedAttributes props, mirroring react-instantsearch's own controls.
Storefront / composer-app developers
As a developer wiring a non-child element (e.g. a sidebar "Reset" link rendered outside the chip slot) to the clear-all action, I want a clear() ref-action on EPClearRefinements, mirroring the pattern used by EPSearchPagination's prevPage / nextPage from PR feat(ep-commerce): enrich EPSearchPagination default slot with Prev/Next chrome #312.
As a developer, I want a Studio canvas preview of both components rendering with mock data when previewing in the editor, with the option to opt out via a previewState prop, matching the existing auto / withData pattern from EPSearchStats, EPSearchPagination, and EPRefinementList.
Pattern adoption (forward-looking)
As a maintainer extending the catalog-search package with future components (autocomplete, hits-per-page, infinite hits, etc. from feat(ep-commerce): catalog-search component coverage gaps #304), I want a shared cloneWithInjectedHandlers helper so I'm not re-implementing composed-onClick logic per component.
As a maintainer, I want the catalog-search components' Pattern C usage to look identical to commerce-providers/commerce's AddToCartButton, so anyone reading either area sees a consistent precedent.
Why now: the PlasmicElement attrs schema in packages/host/src/element-types.ts only accepts static string attributes (disabled, className, id, aria-*, tabIndex, title, role). It cannot express onClick or any expression-bound prop. Pattern C is the canonical workaround across the ecosystem.
A single exported function in a new module under plasmicpkgs/commerce-providers/elastic-path/src/catalog-search/. Signature:
Inputs: a ReactNode child, an object of injected props, a list of prop keys to compose (run designer's handler first, then injected) versus override (replace).
Output: a cloned React element with composed/overridden props, OR the input child unchanged if it isn't a single valid React element (fail-open).
Deep-module shape (per the Ousterhout sense): one small, stable interface; encapsulates the React.isValidElement guard, the prop composition, and the fail-open semantics; used by 5+ call sites; testable in isolation.
New: EPClearRefinements component
Wraps useClearRefinements() from react-instantsearch.
Props (designer-facing): children (slot, single button by default), includedAttributes?: string[], excludedAttributes?: string[], className?, previewState?: "auto" | "withData".
Default slot: a single <button>Clear all</button> element.
Renders a wrapper <div className={className} data-ep-clear-refinements=""> containing the cloned child with onClick (composed with designer's onClick if any) and disabled = !canRefine injected. Multi-element / fragment children render as-is (fail-open).
Publishes DataProvider name="clearRefinementsData" with { canRefine, clear }. The clear function on context is the escape hatch for designers whose layout breaks auto-injection.
Exposes a clear() ref-action so non-child elements can trigger it (parity with other catalog-search components).
Always rendered (never auto-hidden) — the disabled state is the "can't refine right now" signal, preserving layout stability.
Wraps useCurrentRefinements() from react-instantsearch. The hook returns nested data (items per attribute, refinements per value); this component flattens to one chip per (attribute, value) pair.
Props: children (slot, single chip-shaped element by default), includedAttributes?, excludedAttributes? (Algolia default ['query'] is preserved when unset), className?, previewState?.
Default slot: a single hbox chip (rounded border, padding, gap) containing two text elements ("Brand: Leather" + "×"). All styles use longhand only (no shorthand padding / borderRadius) — defensive against known Plasmic codepath bugs with shorthand styles.
Per-iteration DataProvider name="currentRefinementChip" publishes { attribute, attributeLabel, type, value, label, operator?, count?, refine }. refine is zero-arg, pre-bound to remove this specific refinement (the underlying hook's refine takes a refinement object — pre-binding is the designer-friendly affordance). Companion currentRefinementChipIndex ctx for repeater index access.
Renders a wrapper <div className={className} data-ep-current-refinements="" role="list"> with the repeated, cloned chip inside <div role="listitem"> per refinement.
Each repeated chip is cloned with onClick (composed) injected — top-level clone, click bubbles from inner text/×.
Returns null when no refinements are active (the chip row hides entirely — issue spec).
No ref-actions. Clearing all is EPClearRefinements's job; per-chip refine() is on per-item ctx.
Adds one zero-specificity :where() rule for [data-ep-current-refinements] setting display: flex; flex-wrap: wrap; gap: 8px;. This makes the default chip row lay out as a wrapped row out of the box; designer-supplied className always wins. No CSS for EPClearRefinements (single button — no layout concern).
Modified: design-time-data.ts
Adds:
MOCK_CLEAR_REFINEMENTS_DATA (with both canRefine: true and canRefine: false variants for preview / test).
MOCK_CURRENT_REFINEMENT_CHIPS — three mixed sample chips: one facet ("Brand: Leather"), one numeric ("Price ≥ 25"), one hierarchical ("Bags > Leather") — exercises the union of type values designers will encounter.
Existing CatalogSearchData / RefinementItem / etc. types extended to publish the new chip shape (CurrentRefinementChip).
Modified: index.ts and centralized registerAll
Exports EPClearRefinements + registerEPClearRefinements, EPCurrentRefinements + registerEPCurrentRefinements, and the new chip / clear data types. Centralized registration helper updated to register both.
Both components accept includedAttributes and excludedAttributes as string[] props, passed through to the underlying react-instantsearch hooks unchanged.
previewState: "auto" | "withData" matches the established pattern across catalog-search components.
Architectural decisions
Flat repeater for chips (one chip per refinement value, attributes flattened) rather than nested per-attribute groups. Issue feat(ep-commerce): catalog-search component coverage gaps #304 spec endorses this. A nested variant (EPCurrentRefinementsByAttribute) can be added later if a real grouping use-case emerges.
Pre-bound zero-arg refine on per-item ctx (rather than exposing the raw hook signature refine(refinement)). Friendlier in Studio interactions.
Fail-open on multi-element children — render as-is (don't return null like AddToCartButton); the per-item / data-provider context is the documented escape hatch for designers whose structure defeats auto-injection.
Wrap children in a <div> with className and data-ep-* attributes, even though AddToCartButton doesn't (which loses className) — corrects this gap by-design in our adoption.
No backporting of these improvements to AddToCartButton (different package, different scope). The buggy behavior there can be addressed separately.
Testing Decisions
A good test asserts external behavior: which DataProvider contexts are published, what data they carry, that clicks trigger the underlying hook actions, that designer-supplied onClick is called before the injected one, and that the fail-open path renders without crashing. A test that snapshots the exact DOM of the default-slot wrapper or that asserts internal helper return shapes is testing implementation details and should be avoided.
Modules tested
cloneWithInjectedHandlers helper — isolated unit tests against its single function. Cases: valid single child + onClick injection; valid child with existing onClick → composition order; non-element child (string, fragment, array) → fail-open passthrough; multiple compose keys; override-only keys.
EPClearRefinements — integration tests with mocked react-instantsearch.useClearRefinements. Cases: golden path (ctx published, default child cloned, click triggers refine); canRefine: false → injected disabled: true; multi-element children → fail-open render, no crash, ctx still published; designer-supplied onClick on child → composed (designer runs first, then injected); preview-mock path renders without invoking hooks.
EPCurrentRefinements — same approach with mocked useCurrentRefinements. Cases: golden path (flatten + repeat + ctx + click → refine for THAT refinement); empty state (no refinements) → returns null; mixed refinement types (facet + numeric + hierarchical) all render with correct type, operator, label; designer-supplied onClick on chip → composed; multi-element repeated child → fail-open; preview-mock path.
Prior art
plasmicpkgs/commerce-providers/elastic-path/src/catalog-search/__tests__/catalog-search-components.test.tsx already covers EPSearchStats, EPRefinementList, EPSearchPagination, etc. with exactly this approach: mock react-instantsearch hooks, use @testing-library/react to assert published context values via probe components, and assert click handlers fire the mocked refine functions. New tests slot into that file alongside the existing ones; no new test infrastructure needed.
Pagination retro refactor: splitting EPSearchPagination (PR feat(ep-commerce): enrich EPSearchPagination default slot with Prev/Next chrome #312) into atomic EPSearchPagePrev / EPSearchPageNext / EPSearchPageIndicator sub-components, with EPSearchPagination kept as a wrapper for backwards compatibility. This is a separate follow-up issue + PR; it depends on the Pattern C precedent established here but is otherwise independent. Will be filed as its own issue once this PRD's PR is merged.
Backporting Pattern C improvements (className wrapping, fail-open) to AddToCartButton in plasmicpkgs/commerce-providers/commerce. Different package, separate concern.
Nested-by-attribute variant of EPCurrentRefinements (e.g. EPCurrentRefinementsByAttribute). Add if a real use-case emerges.
Outer (non-per-item) context on EPCurrentRefinements exposing canRefine or totalChips. Designers needing those today can use EPSearchStats (nbHits) or EPClearRefinements's clearRefinementsData.canRefine if both components are on the page.
Further Notes
This PRD adopts an established pattern (Pattern C, used 12+ times across 7 plasmicpkgs); it does not introduce a novel one. Risk is correspondingly low.
The cloneWithInjectedHandlers helper is intentionally framed as a deep module (small interface, encapsulated logic, easy to unit-test) and is the natural home for any future enhancements to the composed-onClick semantics across catalog-search components.
Problem Statement
Two affected user types:
Storefront shoppers browsing the catalog-search results page can't:
Designers in Plasmic Studio building those storefronts can't drop in primitives for either UX, because
EPClearRefinementsandEPCurrentRefinementsdon't exist yet (see #304 items #1 and #2).There is also a related developer-experience problem: the existing catalog-search components (
EPSearchPagination,EPRefinementList, etc.) ship editable default chrome but require designers to manually wire onClick interactions in Studio for every action — clear, refine, paginate. Meanwhile, the dominant pattern across plasmicpkgs (used byAddToCartButton,plasmic-cms,antd5,react-aria,dnd-kit,keen-slider,plasmic-tabs— 12+ call sites across 7 packages) isdefaultValue+React.cloneElementto inject behavioral props at render time while keeping the default chrome editable. The catalog-search package hasn't adopted this pattern. The two new components are a natural place to introduce it.Solution
EPClearRefinements(issue feat(ep-commerce): catalog-search component coverage gaps #304 item feat: add basic ep logo #1) andEPCurrentRefinements(issue feat(ep-commerce): catalog-search component coverage gaps #304 item feat: copilot generative ai for editor app #2) as the first catalog-search components using Pattern C.cloneWithInjectedHandlershelper as a deep module — small, isolated, reusable across the 5+ components that will adopt the pattern.onClick(anddisabledwhere applicable) into the default slot, so designers drop the component and it works end-to-end without any Studio interaction wiring. Designers customizing the default chrome (or supplying their ownonClick) compose cleanly: the designer's handler runs first, then the injected one.EPSearchPaginationfrom PR feat(ep-commerce): enrich EPSearchPagination default slot with Prev/Next chrome #312 into atomicEPSearchPagePrev/EPSearchPageNext/EPSearchPageIndicatorsub-components) is a separate follow-up issue / PR — does not block this work.User Stories
Storefront shoppers
Designers in Plasmic Studio
EPClearRefinementsonto a search page and see a working Clear all button on the canvas immediately, without going to Interactions to wireonClick.onClickto$ctx.clearRefinementsData.clearmyself and have it work even when my structure breaks the auto-injection.EPCurrentRefinementsand see a styled chip on the canvas immediately, repeating per active refinement, with dismiss-on-click already wired.$ctx.currentRefinementChip.attributeLabel,.label, etc.) so I can format chips like "Brand: Leather".facet,numeric,hierarchical, etc.) per chip, so I can conditionally style range chips differently from facet chips.includedAttributes/excludedAttributesprops, mirroringreact-instantsearch's own controls.Storefront / composer-app developers
clear()ref-action onEPClearRefinements, mirroring the pattern used byEPSearchPagination'sprevPage/nextPagefrom PR feat(ep-commerce): enrich EPSearchPagination default slot with Prev/Next chrome #312.previewStateprop, matching the existingauto/withDatapattern fromEPSearchStats,EPSearchPagination, andEPRefinementList.Pattern adoption (forward-looking)
cloneWithInjectedHandlershelper so I'm not re-implementing composed-onClick logic per component.commerce-providers/commerce'sAddToCartButton, so anyone reading either area sees a consistent precedent.Implementation Decisions
Pattern C —
defaultValue+React.cloneElementThis PRD adopts the pattern already used by:
plasmicpkgs/commerce-providers/commerce—registerAddToCartButton.tsx(closest spiritual sibling — single button injectingonClick),registerProductQuantity.tsx(form-field props injection)plasmicpkgs/plasmic-cms—components.tsx(4 call sites; injectshref,src,valuefrom CMS data into editable defaults)plasmicpkgs/antd5—registerBreadcrumb.tsx,registerCollapse.tsx,registerPagination.tsx,form/FormItem.tsxplasmicpkgs/react-aria—utils.ts,registerSliderTrack.tsxplasmicpkgs/dnd-kit—sortable.tsx(refs, drag transforms, listeners)plasmicpkgs/keen-slider—index.tsxplasmicpkgs/plasmic-tabs—tabs.tsxWhy now: the PlasmicElement
attrsschema inpackages/host/src/element-types.tsonly accepts static string attributes (disabled,className,id,aria-*,tabIndex,title,role). It cannot expressonClickor any expression-bound prop. Pattern C is the canonical workaround across the ecosystem.Modules
New: shared
cloneWithInjectedHandlershelper (deep module)A single exported function in a new module under
plasmicpkgs/commerce-providers/elastic-path/src/catalog-search/. Signature:ReactNodechild, an object of injected props, a list of prop keys to compose (run designer's handler first, then injected) versus override (replace).Deep-module shape (per the Ousterhout sense): one small, stable interface; encapsulates the
React.isValidElementguard, the prop composition, and the fail-open semantics; used by 5+ call sites; testable in isolation.New:
EPClearRefinementscomponentuseClearRefinements()fromreact-instantsearch.children(slot, single button by default),includedAttributes?: string[],excludedAttributes?: string[],className?,previewState?: "auto" | "withData".<button>Clear all</button>element.<div className={className} data-ep-clear-refinements="">containing the cloned child withonClick(composed with designer's onClick if any) anddisabled = !canRefineinjected. Multi-element / fragment children render as-is (fail-open).DataProvider name="clearRefinementsData"with{ canRefine, clear }. Theclearfunction on context is the escape hatch for designers whose layout breaks auto-injection.clear()ref-action so non-child elements can trigger it (parity with other catalog-search components).parentComponentName: "plasmic-commerce-ep-catalog-search-provider"(matches existing primitives).New:
EPCurrentRefinementscomponentuseCurrentRefinements()fromreact-instantsearch. The hook returns nested data (items per attribute, refinements per value); this component flattens to one chip per(attribute, value)pair.children(slot, single chip-shaped element by default),includedAttributes?,excludedAttributes?(Algolia default['query']is preserved when unset),className?,previewState?.hboxchip (rounded border, padding, gap) containing two text elements ("Brand: Leather" + "×"). All styles use longhand only (no shorthandpadding/borderRadius) — defensive against known Plasmic codepath bugs with shorthand styles.DataProvider name="currentRefinementChip"publishes{ attribute, attributeLabel, type, value, label, operator?, count?, refine }.refineis zero-arg, pre-bound to remove this specific refinement (the underlying hook'srefinetakes a refinement object — pre-binding is the designer-friendly affordance). CompanioncurrentRefinementChipIndexctx for repeater index access.<div className={className} data-ep-current-refinements="" role="list">with the repeated, cloned chip inside<div role="listitem">per refinement.onClick(composed) injected — top-level clone, click bubbles from inner text/×.nullwhen no refinements are active (the chip row hides entirely — issue spec).EPClearRefinements's job; per-chiprefine()is on per-item ctx.parentComponentName: "plasmic-commerce-ep-catalog-search-provider".Modified:
headless-styling.tsAdds one zero-specificity
:where()rule for[data-ep-current-refinements]settingdisplay: flex; flex-wrap: wrap; gap: 8px;. This makes the default chip row lay out as a wrapped row out of the box; designer-suppliedclassNamealways wins. No CSS forEPClearRefinements(single button — no layout concern).Modified:
design-time-data.tsAdds:
MOCK_CLEAR_REFINEMENTS_DATA(with bothcanRefine: trueandcanRefine: falsevariants for preview / test).MOCK_CURRENT_REFINEMENT_CHIPS— three mixed sample chips: one facet ("Brand: Leather"), one numeric ("Price ≥ 25"), one hierarchical ("Bags > Leather") — exercises the union oftypevalues designers will encounter.Existing
CatalogSearchData/RefinementItem/ etc. types extended to publish the new chip shape (CurrentRefinementChip).Modified:
index.tsand centralizedregisterAllExports
EPClearRefinements+registerEPClearRefinements,EPCurrentRefinements+registerEPCurrentRefinements, and the new chip / clear data types. Centralized registration helper updated to register both.API / data contracts
clearRefinementsData = { canRefine: boolean, clear: () => void }currentRefinementChip = { attribute: string, attributeLabel: string, type: 'facet' | 'exclude' | 'disjunctive' | 'hierarchical' | 'numeric' | 'query' | 'tag', value: string | number, label: string, operator?: string, count?: number, refine: () => void }currentRefinementChipIndex = numberincludedAttributesandexcludedAttributesasstring[]props, passed through to the underlyingreact-instantsearchhooks unchanged.previewState: "auto" | "withData"matches the established pattern across catalog-search components.Architectural decisions
EPCurrentRefinementsByAttribute) can be added later if a real grouping use-case emerges.refineon per-item ctx (rather than exposing the raw hook signaturerefine(refinement)). Friendlier in Studio interactions.AddToCartButtonsemantics.AddToCartButton); the per-item / data-provider context is the documented escape hatch for designers whose structure defeats auto-injection.<div>withclassNameanddata-ep-*attributes, even thoughAddToCartButtondoesn't (which losesclassName) — corrects this gap by-design in our adoption.AddToCartButton(different package, different scope). The buggy behavior there can be addressed separately.Testing Decisions
A good test asserts external behavior: which DataProvider contexts are published, what data they carry, that clicks trigger the underlying hook actions, that designer-supplied
onClickis called before the injected one, and that the fail-open path renders without crashing. A test that snapshots the exact DOM of the default-slot wrapper or that asserts internal helper return shapes is testing implementation details and should be avoided.Modules tested
cloneWithInjectedHandlershelper — isolated unit tests against its single function. Cases: valid single child + onClick injection; valid child with existing onClick → composition order; non-element child (string, fragment, array) → fail-open passthrough; multiple compose keys; override-only keys.EPClearRefinements— integration tests with mockedreact-instantsearch.useClearRefinements. Cases: golden path (ctx published, default child cloned, click triggersrefine);canRefine: false→ injecteddisabled: true; multi-element children → fail-open render, no crash, ctx still published; designer-suppliedonClickon child → composed (designer runs first, then injected); preview-mock path renders without invoking hooks.EPCurrentRefinements— same approach with mockeduseCurrentRefinements. Cases: golden path (flatten + repeat + ctx + click →refinefor THAT refinement); empty state (no refinements) → returnsnull; mixed refinement types (facet + numeric + hierarchical) all render with correcttype,operator,label; designer-suppliedonClickon chip → composed; multi-element repeated child → fail-open; preview-mock path.Prior art
plasmicpkgs/commerce-providers/elastic-path/src/catalog-search/__tests__/catalog-search-components.test.tsxalready coversEPSearchStats,EPRefinementList,EPSearchPagination, etc. with exactly this approach: mockreact-instantsearchhooks, use@testing-library/reactto assert published context values via probe components, and assert click handlers fire the mocked refine functions. New tests slot into that file alongside the existing ones; no new test infrastructure needed.Out of Scope
This PRD intentionally excludes:
EPSearchAutocomplete(feat: add init ep hosting endpoints #3),EPHitsPerPage(feat: use elastic path domain for canvas hosting #4),EPInfiniteHits(build: deploy workflows #5),EPMenu(feat: Initial WIP Elastic Path commerce provider package #6),EPToggleRefinement(add terraform #7),EPNumericMenu(add: storefront.elasticpath.com subdomain + cert #8),EPSearchEmpty(deploy integration on merge to main #9), and thehitsPerPageprovider-prop semantics fix (Deploy integration merge main #10). These remain on the parent issue's backlog.EPSearchPagination(PR feat(ep-commerce): enrich EPSearchPagination default slot with Prev/Next chrome #312) into atomicEPSearchPagePrev/EPSearchPageNext/EPSearchPageIndicatorsub-components, withEPSearchPaginationkept as a wrapper for backwards compatibility. This is a separate follow-up issue + PR; it depends on the Pattern C precedent established here but is otherwise independent. Will be filed as its own issue once this PRD's PR is merged.AddToCartButtoninplasmicpkgs/commerce-providers/commerce. Different package, separate concern.EPCurrentRefinements(e.g.EPCurrentRefinementsByAttribute). Add if a real use-case emerges.EPCurrentRefinementsexposingcanRefineortotalChips. Designers needing those today can useEPSearchStats(nbHits) orEPClearRefinements'sclearRefinementsData.canRefineif both components are on the page.Further Notes
cloneWithInjectedHandlershelper is intentionally framed as a deep module (small interface, encapsulated logic, easy to unit-test) and is the natural home for any future enhancements to the composed-onClick semantics across catalog-search components.