Search: jetpack-search/filter-static block (SEARCH-219, 3/3)#49040
Search: jetpack-search/filter-static block (SEARCH-219, 3/3)#49040kangzj wants to merge 3 commits into
Conversation
|
Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.
Interested in more tips and information?
|
|
Thank you for your PR! When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:
This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖 Follow this PR Review Process:
If you have questions about anything, reach out in #jetpack-developers for guidance! |
Code Coverage Summary3 files are newly checked for coverage.
Full summary · PHP report · JS report If appropriate, add one of these labels to override the failing coverage check:
Covered by non-unit tests
|
a45f65d to
ea9574c
Compare
58dccee to
eaa5154
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…on, normalizeVariation test (PR #49040) Addresses copilot-swe-agent's review: - Changelog: 'Search: new `...` block' → 'Search: add `...` block' (imperative mood per the changelogger convention). - Accessibility: <fieldset> now always emits a <legend> so the radio group has a programmatic name. When both the block-level label override and the server-supplied name are empty, the legend falls back to the filter_id — that case is host-site misconfiguration but the markup degrades gracefully instead of leaving the fieldset unnamed for screen readers. - Icon collision: filter-static was using `postTerms`, which already belongs to the filter-checkbox 'post_tag' variation. The FILTER_CHECKBOX_VARIATION_ICONS docblock guarantees no-duplicates across the two icon maps. Switched filter-static to `formatListNumbered` (still semantically fitting: a numbered/preset radio list of server-defined choices, visually distinct from filter-checkbox's `formatListBullets`). - Test coverage: new tests/js/search-blocks/filter-static-edit.test.js pins `normalizeVariation` — anything other than the literal string 'tabbed' collapses to 'sidebar', matching `Filter_Static::normalize_variation` on the PHP side. A drift between these two would scope the editor preview to a different filter subset than the front-end render. Skipped: - The `__()` third-arg-0 pattern in edit.js intentionally mirrors the same convention in filter-checkbox/edit.js (4 sites). Diverging here alone would create drift; leaving as a future repo-wide cleanup if the underlying minification issue is ever documented. <!-- jp-loop -->
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…g (PR #49038) Block-side static-filter radios need a reactive checked binding so they stay in sync with the store across paths that mutate state.staticFilterSelections outside the radio's own change event — the clearFilters() reset and the popstate-driven URL rewind. Without this, radios visually keep their last clicked state even after the store has cleared the slice. The callback reads the per-<li> context (filterKey + optionValue) that the filter-static block's render.php will emit, and compares against state.staticFilterSelections[filterKey]. Each radio in render.php then binds: data-wp-bind--checked="callbacks.isStaticFilterSelected" Server-rendered `checked` still seeds first paint; this takes over after hydration. Surfaced during the review cycle on PR #49040. <!-- jp-loop -->
308ad57 to
7da8d9d
Compare
…on, normalizeVariation test (PR #49040) Addresses copilot-swe-agent's review: - Changelog: 'Search: new `...` block' → 'Search: add `...` block' (imperative mood per the changelogger convention). - Accessibility: <fieldset> now always emits a <legend> so the radio group has a programmatic name. When both the block-level label override and the server-supplied name are empty, the legend falls back to the filter_id — that case is host-site misconfiguration but the markup degrades gracefully instead of leaving the fieldset unnamed for screen readers. - Icon collision: filter-static was using `postTerms`, which already belongs to the filter-checkbox 'post_tag' variation. The FILTER_CHECKBOX_VARIATION_ICONS docblock guarantees no-duplicates across the two icon maps. Switched filter-static to `formatListNumbered` (still semantically fitting: a numbered/preset radio list of server-defined choices, visually distinct from filter-checkbox's `formatListBullets`). - Test coverage: new tests/js/search-blocks/filter-static-edit.test.js pins `normalizeVariation` — anything other than the literal string 'tabbed' collapses to 'sidebar', matching `Filter_Static::normalize_variation` on the PHP side. A drift between these two would scope the editor preview to a different filter subset than the front-end render. Skipped: - The `__()` third-arg-0 pattern in edit.js intentionally mirrors the same convention in filter-checkbox/edit.js (4 sites). Diverging here alone would create drift; leaving as a future repo-wide cleanup if the underlying minification issue is ever documented. <!-- jp-loop -->
…nt (PR #49040) Two findings from claude[bot]'s re-review on PR #49040: - Bug: Radio state drifted from the store after clearFilters() or handlePopState() because the radios had no reactive checked binding — the change event only fires on user-initiated transitions, so any state mutation through other paths left the DOM stale. Fix: - Add per-<li> data-wp-context with the option's value so the new callbacks.isStaticFilterSelected (landed in the foundation PR #49038) can compare it against state.staticFilterSelections. - Add data-wp-bind--checked="callbacks.isStaticFilterSelected" to each radio input. Server-rendered `checked` still seeds first paint; the reactive binding takes over after IA hydration. - Doc nit (copilot): FILTER_CHECKBOX_VARIATION_ICONS docblock count was '19 + 8'; updated to '20 + 8' to reflect the new filter-static entry in BLOCK_ICONS. Also deferred: the 're-pick a checked radio to deselect' line in the prior testing instructions is unreachable (browser change event semantics) — removing it from the PR body in a follow-up edit instead of carrying a separate code change. <!-- jp-loop -->
eb464f6 to
17e8213
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
7da8d9d to
350ab3d
Compare
…on, normalizeVariation test (PR #49040) Addresses copilot-swe-agent's review: - Changelog: 'Search: new `...` block' → 'Search: add `...` block' (imperative mood per the changelogger convention). - Accessibility: <fieldset> now always emits a <legend> so the radio group has a programmatic name. When both the block-level label override and the server-supplied name are empty, the legend falls back to the filter_id — that case is host-site misconfiguration but the markup degrades gracefully instead of leaving the fieldset unnamed for screen readers. - Icon collision: filter-static was using `postTerms`, which already belongs to the filter-checkbox 'post_tag' variation. The FILTER_CHECKBOX_VARIATION_ICONS docblock guarantees no-duplicates across the two icon maps. Switched filter-static to `formatListNumbered` (still semantically fitting: a numbered/preset radio list of server-defined choices, visually distinct from filter-checkbox's `formatListBullets`). - Test coverage: new tests/js/search-blocks/filter-static-edit.test.js pins `normalizeVariation` — anything other than the literal string 'tabbed' collapses to 'sidebar', matching `Filter_Static::normalize_variation` on the PHP side. A drift between these two would scope the editor preview to a different filter subset than the front-end render. Skipped: - The `__()` third-arg-0 pattern in edit.js intentionally mirrors the same convention in filter-checkbox/edit.js (4 sites). Diverging here alone would create drift; leaving as a future repo-wide cleanup if the underlying minification issue is ever documented. <!-- jp-loop -->
17e8213 to
b10667e
Compare
…nt (PR #49040) Two findings from claude[bot]'s re-review on PR #49040: - Bug: Radio state drifted from the store after clearFilters() or handlePopState() because the radios had no reactive checked binding — the change event only fires on user-initiated transitions, so any state mutation through other paths left the DOM stale. Fix: - Add per-<li> data-wp-context with the option's value so the new callbacks.isStaticFilterSelected (landed in the foundation PR #49038) can compare it against state.staticFilterSelections. - Add data-wp-bind--checked="callbacks.isStaticFilterSelected" to each radio input. Server-rendered `checked` still seeds first paint; the reactive binding takes over after IA hydration. - Doc nit (copilot): FILTER_CHECKBOX_VARIATION_ICONS docblock count was '19 + 8'; updated to '20 + 8' to reflect the new filter-static entry in BLOCK_ICONS. Also deferred: the 're-pick a checked radio to deselect' line in the prior testing instructions is unreachable (browser change event semantics) — removing it from the PR body in a follow-up edit instead of carrying a separate code change. <!-- jp-loop -->
350ab3d to
0c55d44
Compare
…on, normalizeVariation test (PR #49040) Addresses copilot-swe-agent's review: - Changelog: 'Search: new `...` block' → 'Search: add `...` block' (imperative mood per the changelogger convention). - Accessibility: <fieldset> now always emits a <legend> so the radio group has a programmatic name. When both the block-level label override and the server-supplied name are empty, the legend falls back to the filter_id — that case is host-site misconfiguration but the markup degrades gracefully instead of leaving the fieldset unnamed for screen readers. - Icon collision: filter-static was using `postTerms`, which already belongs to the filter-checkbox 'post_tag' variation. The FILTER_CHECKBOX_VARIATION_ICONS docblock guarantees no-duplicates across the two icon maps. Switched filter-static to `formatListNumbered` (still semantically fitting: a numbered/preset radio list of server-defined choices, visually distinct from filter-checkbox's `formatListBullets`). - Test coverage: new tests/js/search-blocks/filter-static-edit.test.js pins `normalizeVariation` — anything other than the literal string 'tabbed' collapses to 'sidebar', matching `Filter_Static::normalize_variation` on the PHP side. A drift between these two would scope the editor preview to a different filter subset than the front-end render. Skipped: - The `__()` third-arg-0 pattern in edit.js intentionally mirrors the same convention in filter-checkbox/edit.js (4 sites). Diverging here alone would create drift; leaving as a future repo-wide cleanup if the underlying minification issue is ever documented. <!-- jp-loop -->
…nt (PR #49040) Two findings from claude[bot]'s re-review on PR #49040: - Bug: Radio state drifted from the store after clearFilters() or handlePopState() because the radios had no reactive checked binding — the change event only fires on user-initiated transitions, so any state mutation through other paths left the DOM stale. Fix: - Add per-<li> data-wp-context with the option's value so the new callbacks.isStaticFilterSelected (landed in the foundation PR #49038) can compare it against state.staticFilterSelections. - Add data-wp-bind--checked="callbacks.isStaticFilterSelected" to each radio input. Server-rendered `checked` still seeds first paint; the reactive binding takes over after IA hydration. - Doc nit (copilot): FILTER_CHECKBOX_VARIATION_ICONS docblock count was '19 + 8'; updated to '20 + 8' to reflect the new filter-static entry in BLOCK_ICONS. Also deferred: the 're-pick a checked radio to deselect' line in the prior testing instructions is unreachable (browser change event semantics) — removing it from the PR body in a follow-up edit instead of carrying a separate code change. <!-- jp-loop -->
b10667e to
63d0b9c
Compare
🤖 Review-cycle summary — initial →
|
| Source | Item | Resolution |
|---|---|---|
copilot-swe-agent |
Changelog imperative mood ("new" → "add") | (eb464f6b91) |
copilot-swe-agent |
missing for a11y when label + name empty | Always emits , falls back to filter_id (eb464f6b91) |
copilot-swe-agent |
Non-standard __() third arg | Acknowledged — kept consistent with filter-checkbox; deferred as repo-wide cleanup |
copilot-swe-agent |
postTerms icon collision with FILTER_CHECKBOX_VARIATION_ICONS.post_tag | Switched to formatListNumbered (eb464f6b91) |
copilot-swe-agent |
No unit test for normalizeVariation | Added filter-static-edit.test.js (eb464f6b91) |
claude[bot] |
Bug: radio state drifts from store after clearFilters() / handlePopState() | Added per-17e8213b45); pairs with isStaticFilterSelected callback added in PR Search: store foundation for static-filter URL contract (SEARCH-219, 1/3) #49038 |
claude[bot] |
Bug: testing instruction claimed re-pick deselects (unreachable via radio change event) | Removed from PR body; setStaticFilter toggle branch stays as defensive no-op |
copilot-swe-agent |
Icon docblock count "19 + 8" → "20 + 8" | (17e8213b45) |
New file added: tests/js/search-blocks/filter-static-edit.test.js (3 tests for normalizeVariation).
Reviewer outcome (last review):
claude[bot]: "Ready to merge. All previously reported bugs are fixed and verified. No new issues found."copilot-swe-agent: "LGTM — no findings."
CI: 57 functional checks pass; Code coverage requirement pending — non-blocking.
…1/3) (#49038) * Search: store foundation for static-filter URL contract (SEARCH-219) Adds the JS store plumbing the upcoming `jetpack-search/filter-static` block depends on. Foundation-only — inert until a block contributes a `kind: 'static'` filterConfig entry. - `store/url-state.js`: `stateToUrlParams` accepts a `staticFilterSelections` slice and serializes each `kind === 'static'` entry as a scalar `?filter_id=value` param (no `[]` suffix). `urlParamsToState` mirrors the parse direction — scalar params land in `staticFilterSelections` only when their key is registered as kind=static, so plugin-emitted scalar params can't pollute state. - `store/index.js`: new `staticFilterSelections` state slice, `setStaticFilter` (replace, not toggle) and `onStaticFilterChange` actions, `gateStaticFilterSelections` helper. Slice threaded through `syncToUrl`, `handlePopState`, `clearFilters`, `initialize()` re-gating, the search-request builder, and the `hasActiveFilters` / `activeFilterCount` getters. - `store/api.js`: `buildStaticFilterClauses` emits ES `term` clauses keyed by the static filter id; folded into the existing `bool.must` pipeline alongside dynamic facet filters, static post-type clauses, and the price range. URL contract matches the legacy instant-search overlay's static-filter shape (scalar, single value per key) so deep links round-trip between the two surfaces. Precedent: `priceRange` is a sibling slice with the same shape (see `search-blocks/AGENTS.md` "URL format"). Test coverage: - url-state.test.js (+8 cases): serialize gated on kind=static; parse pulls only configured keys; ignores unrelated scalar params; empty values dropped; last-write-wins on duplicate params. - store.test.js (+7 cases): setStaticFilter replace/clear; clearFilters resets the slice; gateStaticFilterSelections rule set including a prototype-pollution defence. - api.test.js (+6 cases): buildStaticFilterClauses gating + empty-value drop; buildSearchUrl integration alone and combined with dynamic filters. * Address review: orphaned setFilter JSDoc, RESERVED_PARAMS guard, gateStaticFilterSelections return shape (PR #49038) - Move the orphaned setFilter() JSDoc block back to sit immediately above the setFilter() declaration. The insertion of setStaticFilter() pushed the block out of place; JSDoc processors attach to the next decl, so setFilter() was effectively undocumented. - Add RESERVED_PARAMS guard to stateToUrlParams' static-filter serializer so a misconfigured filter whose filter_id matches a reserved param (s / orderby / min_price / max_price) can't silently overwrite the search query / sort / price params. Mirrors urlParamsToState which already filtered these. - Align gateStaticFilterSelections' return shape with gateActiveFilters: now returns { gated, droppedAny } so callers in handlePopState() and initialize() can skip the state write when the gate was a no-op. The prior length-comparison fallback in initialize() collapses to a single boolean check. Addresses claude[bot]'s review on PR #49038. <!-- jp-loop --> * Search: add isStaticFilterSelected callback for reactive radio binding (PR #49038) Block-side static-filter radios need a reactive checked binding so they stay in sync with the store across paths that mutate state.staticFilterSelections outside the radio's own change event — the clearFilters() reset and the popstate-driven URL rewind. Without this, radios visually keep their last clicked state even after the store has cleared the slice. The callback reads the per-<li> context (filterKey + optionValue) that the filter-static block's render.php will emit, and compares against state.staticFilterSelections[filterKey]. Each radio in render.php then binds: data-wp-bind--checked="callbacks.isStaticFilterSelected" Server-rendered `checked` still seeds first paint; this takes over after hydration. Surfaced during the review cycle on PR #49040. <!-- jp-loop --> * Address re-review nits: afterEach reset + undefined-slice test (PR #49038) Two non-blocker nits from claude[bot]'s re-review: - afterEach in the 'store callbacks' describe block now resets state.staticFilterSelections to {} so the assignment at the end of the isStaticFilterSelected test doesn't leak into later cases in the same block. Currently benign (no downstream reader), but defensive. - Add a test pinning the ?. guard on state.staticFilterSelections: when the slice is undefined (PHP seed hasn't populated it yet, e.g. on a page with no filter-static block), the callback returns false instead of crashing. <!-- jp-loop --> * Add integration coverage for staticFilterSelections threading (PR #49038) Pre-existing 'Code coverage requirement' check flagged that the threading paths through hasActiveFilters / activeFilterCount / syncToUrl weren't exercised end-to-end. Added: - hasActiveFilters + activeFilterCount include staticFilterSelections — counts entries with non-empty values, treats empty-string ('cleared') entries as inactive. - syncToUrl writes static-filter selections as scalar params alongside other state — regression guard against a future change forgetting to thread the slice through pushStateToUrl. - beforeEach now resets state.staticFilterSelections so subsequent tests don't inherit leftover values from earlier ones (the new syncToUrl test would otherwise leak its assignment forward). <!-- jp-loop -->
0c55d44 to
20da295
Compare
The user-facing block, completing the three-part SEARCH-219 series.
Mirrors the legacy instant-search overlay's static-filter widget as a
single-select radio list whose options come from server config (no
per-instance editor UI for values). Selections round-trip as scalar
`?filter_id=value` URL params via the JS plumbing landed in part 1/3
and the server-side helper landed in part 2/3.
- `blocks/filter-static/block.json` — three attributes:
- `variation` (sidebar | tabbed) scopes the block to filters
registered with the matching variation.
- `filterId` optionally narrows to a single registered filter; empty
renders every filter for the variation.
- `label` overrides the server-supplied filter name.
- `blocks/filter-static/render.php` — pulls the configured entries via
`Filter_Static::filters_for_variation()`, merges the URL-seeded
selection (falls back to the entry's `selected` default), pushes
`filterConfigs[$id]` + `staticFilterSelections[$id]` into the shared
Interactivity state, and emits one `<fieldset>` per entry with
`<input type="radio" name="<filter_id>">` for native single-select
grouping. `data-wp-on--change="actions.onStaticFilterChange"` wires
each input to the store action.
- `blocks/filter-static/edit.js` — editor preview. Inspector exposes
variation, label override, and a filter picker populated via the
REST endpoint from part 2/3. Empty-state Placeholder when no static
filters are registered. Sample radios so the layout previews in
place.
- `blocks/filter-static/view.js` — bare `import 'jetpack-search/store'`
to register the shared store module dependency.
- `blocks/filter-static/style.scss` — minimal styles that reuse the
existing filter list/label partials.
- `editor/register-blocks.js` + `editor/icons.js` — register the
block's edit component and assign the brand-green `postTerms` icon.
- Changelog entry (minor, type=added).
Falls under the existing `jetpack_search_blocks_enabled` gate; no new
flag.
…on, normalizeVariation test (PR #49040) Addresses copilot-swe-agent's review: - Changelog: 'Search: new `...` block' → 'Search: add `...` block' (imperative mood per the changelogger convention). - Accessibility: <fieldset> now always emits a <legend> so the radio group has a programmatic name. When both the block-level label override and the server-supplied name are empty, the legend falls back to the filter_id — that case is host-site misconfiguration but the markup degrades gracefully instead of leaving the fieldset unnamed for screen readers. - Icon collision: filter-static was using `postTerms`, which already belongs to the filter-checkbox 'post_tag' variation. The FILTER_CHECKBOX_VARIATION_ICONS docblock guarantees no-duplicates across the two icon maps. Switched filter-static to `formatListNumbered` (still semantically fitting: a numbered/preset radio list of server-defined choices, visually distinct from filter-checkbox's `formatListBullets`). - Test coverage: new tests/js/search-blocks/filter-static-edit.test.js pins `normalizeVariation` — anything other than the literal string 'tabbed' collapses to 'sidebar', matching `Filter_Static::normalize_variation` on the PHP side. A drift between these two would scope the editor preview to a different filter subset than the front-end render. Skipped: - The `__()` third-arg-0 pattern in edit.js intentionally mirrors the same convention in filter-checkbox/edit.js (4 sites). Diverging here alone would create drift; leaving as a future repo-wide cleanup if the underlying minification issue is ever documented. <!-- jp-loop -->
…nt (PR #49040) Two findings from claude[bot]'s re-review on PR #49040: - Bug: Radio state drifted from the store after clearFilters() or handlePopState() because the radios had no reactive checked binding — the change event only fires on user-initiated transitions, so any state mutation through other paths left the DOM stale. Fix: - Add per-<li> data-wp-context with the option's value so the new callbacks.isStaticFilterSelected (landed in the foundation PR #49038) can compare it against state.staticFilterSelections. - Add data-wp-bind--checked="callbacks.isStaticFilterSelected" to each radio input. Server-rendered `checked` still seeds first paint; the reactive binding takes over after IA hydration. - Doc nit (copilot): FILTER_CHECKBOX_VARIATION_ICONS docblock count was '19 + 8'; updated to '20 + 8' to reflect the new filter-static entry in BLOCK_ICONS. Also deferred: the 're-pick a checked radio to deselect' line in the prior testing instructions is unreachable (browser change event semantics) — removing it from the PR body in a follow-up edit instead of carrying a separate code change. <!-- jp-loop -->
63d0b9c to
6db2dc5
Compare
|
This needs to be tested on p2s before shipping... 👷 |
Third of three sequential PRs for SEARCH-219. Base branch: #49039 (review parts 1 and 2 first).
jetpack-search/filter-staticblock + editor wiring + changelog.Proposed changes
block.json— three attributes:variation(sidebar/tabbed scopes which filters render),filterId(optionally narrow to one registered filter; empty renders all matching the variation),label(override the server-supplied name). Noparent/ancestorconstraint — works bare or insidefilters/filters-popover.render.php— pulls entries viaFilter_Static::filters_for_variation()(from part 2), merges the URL-seeded selection (falls back to the entry'sselecteddefault), pushesfilterConfigs[$id]+staticFilterSelections[$id]into the shared Interactivity state, and emits one<fieldset>per entry with<input type="radio" name="<filter_id>">for native single-select grouping.data-wp-on--change="actions.onStaticFilterChange"wires each input to the store action (from part 1).edit.js— inspector exposes the three attributes; the filter picker is populated via the REST endpoint from part 2. Empty-state Placeholder when no static filters are registered. Sample radios so the layout previews in place.view.js— bareimport 'jetpack-search/store'(mandatory persearch-blocks/AGENTS.md).style.scss— minimal styles, reuses existing filter list/label partials.editor/register-blocks.js+editor/icons.js— register the edit component and assign the brand-greenpostTermsicon.Falls under the existing
jetpack_search_blocks_enabledgate; no new flag.Related product discussion/links
Does this pull request change what data or activity we track or use?
No.
Testing instructions
End-to-end smoke test on a Search 3.0 blocks-enabled site:
pnpm jetpack build search --deps.search-results. Inspector lists "Section" in the Filter picker (REST round-trip from part 2).?section=guides(scalar — no[]). Network shows one search request withbool.filtercarrying{ term: { section: 'guides' } }.?section=guides→ radio stays checked (server-renderedcheckedattribute viaFilter_Static::parse_url_selections()).sectionreplaces (does not append).[]) → block renders nothing on the front end; editor Placeholder shows the empty-state message.