Skip to content

feat(ep-commerce): headless styling contract + EPSearchBox provider refactor (#305, #308)#309

Merged
field123 merged 1 commit into
masterfrom
feat/search-page-iter
May 1, 2026
Merged

feat(ep-commerce): headless styling contract + EPSearchBox provider refactor (#305, #308)#309
field123 merged 1 commit into
masterfrom
feat/search-page-iter

Conversation

@field123
Copy link
Copy Markdown
Collaborator

@field123 field123 commented May 1, 2026

Summary

Two layered PRDs delivered together because PRD #308 is the architectural completion of PRD #305:

Changes

New files

  • headless-styling.ts — deep module: idempotent injectHeadlessStyles() + useHeadlessStyling() hook. Mounts a single <style data-ep-headless-styles> tag with :where() rules at zero specificity for [data-ep-search-box] (legacy, still useful for non-provider use) and [data-ep-catalog-search-provider].
  • __tests__/headless-styling-contract.tsxdescribeHeadlessStylingContract shared test helper: asserts className lands on documented leaf, no inline appearance styles, editor + runtime structurally equivalent.

Modified

  • EPSearchBox.tsx — provider-only, no DOM. forwardRef + useImperativeHandle exposes setValue(value) (debounced) and clear() ref-actions. Wraps children in <DataProvider name="searchFieldData" data={{value, displayValue, isEmpty}}>. Deprecated props (placeholder/autoFocus/showClear) preserved as hidden no-ops (per pattern_bundle_invariant_orphans) so existing project bundles stay valid.
  • EPCatalogSearchProvider.tsx — dropped DEFAULT_PROVIDER_WRAPPER_STYLE from six render sites; structural CSS now ships via the :where() block.
  • EPSearchHits.tsx — added data-ep-search-hits attribute for contract-test consistency. The inline gridStyle pattern is preserved (the only justified inline-style workaround in the catalog-search codebase, due to Plasmic's className-strip on display: grid).
  • design-time-data.ts — new SearchFieldData type and MOCK_SEARCH_FIELD_DATA.
  • __tests__/catalog-search-components.test.tsx — 45 contract assertions (5 per component × 9 components, except EPSearchBox which has its own behavioural tests). 8 new EPSearchBox provider tests covering ref-action behaviour, debounce, displayValue divergence during debounce, and meta correctness.
  • README.md — new ## Styling contract section with the four-rule contract, leaf-element table per component, :where() pattern guidance, Composing EPSearchBox walkthrough, migration notes.

Migration

  • Existing EPSearchBox instances will lose their inline-rendered chrome. Re-author the slot with a Plasmic <input> and <button>, then bind value and wire interactions per the README's "Composing EPSearchBox" section.
  • The example app's project (9iSL9GyrJNp9ebWzxaHtM5) was re-authored via the Plasmic MCP during this work; the /products page now shows a fully styled search input with all designer-set appearance properties reaching the live DOM.

Test plan

  • yarn test in plasmicpkgs/commerce-providers/elastic-path — 1522 tests pass (no regressions, 8 new EPSearchBox tests, 45 contract assertions across 9 components).
  • yarn build clean (tsdx + server entry).
  • Live verified at http://localhost:3456/products:
    • The styled search input renders with designer-set border/radius/padding/font/background — confirms TplTag styles are NOT subject to TPL_COMPONENT_PROPS filtering.
    • searchFieldData propagates to slot children via React context (verified via fiber inspection).
    • Page renders cleanly with no console errors after the safe-fallback binding ($ctx.searchFieldData && $ctx.searchFieldData.value || "").

Outstanding (not blocking this PR)

  • Studio-UI step required to wire ref-action interactions on the slot's input/button. Plasmic's canonical invokeRefAction action type is not exposed by the MCP's interaction.add tool (filed as gap add enhanced container insights #81 in the EP MCP feedback tracker). The customFunction-with-$refs workaround saves but doesn't fire ref-actions at runtime. To complete the example app's wiring: in Studio, click the input → add onChange interaction → "Run action" → EP Search BoxsetValueevent.target.value. Same for the clear button's onClickclear.
  • Two related MCP gaps surfaced by this work and filed separately: build: trigger fresh terraform plans #79 (Plasmic codegen lag after MCP slot edits) and build: add ingress and egress rules for deploy #82 (Plasmic loader module cache stale despite alwaysFresh: true).

Closes #305.
Closes #308.

…efactor (#305, #308)

PRD #305 - Headless styling contract for the nine catalog-search components:

- New `headless-styling.ts` deep module: idempotent injection of a single
  `<style data-ep-headless-styles>` tag with `:where()` rules at zero
  specificity, encapsulating the structural CSS the components need
  (`position: relative` for the search box wrapper, `width: 100%; align-self:
  stretch` for the catalog provider).
- New `assertHeadlessStylingContract` test helper asserts (a) className
  lands on the documented leaf, (b) no inline appearance styles, (c) editor
  and runtime renders produce structurally equivalent leaves.
- Refactored EPCatalogSearchProvider: dropped DEFAULT_PROVIDER_WRAPPER_STYLE
  from six render sites; structural CSS now ships from the :where() block.
- Refactored EPSearchHits: added data-ep-search-hits attribute for contract
  test consistency; the gridStyle inline pattern is preserved (justified
  workaround for Plasmic's className-strip on display/grid).
- README documents the four-rule contract, leaf-element table, structural
  CSS pattern, and migration notes.

PRD #308 - EPSearchBox slot composition refactor:

EPSearchBox is now a render-children-only provider. It calls
useSearchBox(), manages debounce, and exposes searchFieldData (value,
displayValue, isEmpty) via DataProvider plus setValue/clear refActions.
The visible chrome (input, clear button) is owned by the designer - they
drop Plasmic-controlled `<input>` and `<button>` into the slot, which
escape Plasmic's TPL_COMPONENT_PROPS appearance filter.

- New SearchFieldData type in design-time-data.ts.
- EPSearchBox forwardRef + useImperativeHandle exposes setValue (debounced)
  and clear (cancels pending refine, resets state).
- Metadata change is additive: deprecated placeholder/autoFocus/showClear
  props preserved as hidden no-ops to keep existing project bundles valid
  (per pattern_bundle_invariant_orphans).
- README "Composing EPSearchBox" section explains the new wiring contract.

Tests:

- 45 contract assertions (5 per component x 9 components, except
  EPSearchBox which has its own behavioural tests).
- 8 new EPSearchBox provider tests: searchFieldData exposure, setValue
  updates value, setValue debounces refine, clear resets and calls
  useSearchBox().clear(), displayValue diverges from value during debounce,
  previewState='withData' forces mock outside editor, meta exposes
  refActions and providesData.
- All 1522 EP commerce jest tests pass.
@field123 field123 merged commit 452f2ab into master May 1, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant