Skip to content

Open the meta query builder in a modal with condition cards, nested groups, and a placeholder reference - #179

Open
ryanwelcher wants to merge 15 commits into
feature/dynamic-placeholdersfrom
feature/meta-query-modal
Open

ryanwelcher wants to merge 15 commits into
feature/dynamic-placeholdersfrom
feature/meta-query-modal

Conversation

@ryanwelcher

@ryanwelcher ryanwelcher commented Sep 8, 2026 •

Copy link
Copy Markdown
Owner

Summary

Moves the Meta Query builder out of the inspector flyout and into a full modal, then reworks the builder inside it.

Stacked on feature/dynamic-placeholders so this diff only contains the modal work. The placeholder branch should land first.

  • Modal shell. A generic QueryBuilderModal component (trigger button + core Modal with a sticky footer). The trigger keeps its existing accessible name so prior e2e specs pass untouched.
  • Condition cards. Each condition shows key, compare and type on one row with the value below. The relation control moves to the top of the modal as an All/Any toggle. Add and Reset live in the footer. The sidebar shows a condition count while the modal is closed.
  • Placeholder reference panel. An info button in the modal footer opens a reference-only popover listing every registered dynamic placeholder by label and description, grouped into collapsible sections that open one at a time. Hidden by default, no search box, no literal tokens shown, and no click-to-insert (the Meta Value field's own suggestions handle insertion). Hidden entirely when no placeholders are registered.
  • Operator and type guidance. Text-only operators (LIKE, REGEXP, RLIKE and negations) are hidden for numeric and date types unless a saved block already uses them. EXISTS / NOT EXISTS hide the value field and clear the stored value. IN and BETWEEN show input hints. DATE, DATETIME and TIME types get an inline picker that writes the format WP_Query expects.
  • Nested condition groups. An entry in meta_query.queries may now be a group with its own relation and nested queries. The Meta_Query trait recurses and emits the nested clauses WP_Query expects. Flat arrays produce identical output to before, and groups left without conditions are dropped. Nesting is capped at two levels in the UI. Fixes Allow nested meta_query with relation setting #100.
  • Advanced mode. Compare and type sit behind a per-condition Advanced toggle. A condition already using non-default values shows them and locks the toggle on, so nothing saved is ever hidden. The Remove action moves to a footer row under the value.
  • Token field fixes. Suggestion lists now overlay instead of pushing content down, which was shifting controls between mousedown and mouseup and swallowing clicks. The overlay also hides once a single-token field holds its token.

Testing

  • npm run test:unit — 209 tests, 344 assertions, all passing. New coverage in tests/unit/Meta_Query_Tests.php for nested groups, empty-group dropping and flat-array parity.
  • npm run lint:js and npm run build — clean.
  • New Playwright spec tests/e2e/tests/meta-query-modal.spec.ts covers the modal, cards, placeholder column, operator gating, groups and Advanced mode. Not run in this session (needs a wp-env instance).

Add a generic QueryBuilderModal shell (trigger button plus core Modal)
and render the existing meta builder content inside it unchanged. Edits
still write to block attributes live. The trigger keeps its accessible
name so existing e2e specs pass untouched.
Each condition shows key, compare, and type on one row with the value
below; the Advanced mode toggle and its forced-on logic are removed.
The relation control moves to the top of the modal as an All/Any
toggle, Add and Reset live in a sticky footer provided by the shell,
and the sidebar shows a condition count summary while the modal is
closed.
List every registered dynamic placeholder with its label and
description beside the conditions, filterable by text. Clicking an
entry writes its token into the most recently focused Meta Value
field; with no field focused it explains what to do instead. The
column is hidden when no placeholders are registered.
Hide text-only operators (LIKE, REGEXP, RLIKE and their negations) for
numeric and date types while keeping any operator a saved block already
uses. EXISTS and NOT EXISTS hide the value field and clear the stored
value. IN and BETWEEN show input hints, and DATE, DATETIME, and TIME
types offer an inline picker that writes the format WP_Query expects.

Also overlay the token field suggestion lists instead of pushing
content down: an inline list collapsing on blur shifted the controls
below it between mousedown and mouseup and swallowed the click.
An entry in meta_query.queries may now be a group with its own relation
and nested queries. The Meta_Query trait recurses through groups and
emits the nested clauses WP_Query expects; flat arrays produce the same
output as before, and groups left without conditions are dropped.

In the editor a group renders as a bordered card with its own All/Any
toggle, its conditions, and an Add condition action. Groups are added
from the modal footer only, capping nesting at two levels. The sidebar
summary counts conditions across groups, and placeholder insertion
finds the focused condition at any depth.

Also hide a token field's suggestion overlay once it holds its single
token, so the overlay no longer covers the controls beneath it.

Fixes #100.
Compare and type only matter when they leave the defaults, so they sit
behind a per-condition Advanced mode toggle again. A condition already
using non-default values shows them and locks the toggle on, so nothing
saved is ever hidden. The Remove query button moves to a footer row
under the value field alongside the toggle.
The meta query modal's placeholder column was a sticky sidebar with a
search box and click-to-insert entries. With only two dozen placeholders
the search added little, and the Meta Value field already offers the
same list as suggestions, so click-to-insert duplicated it.

Replace the column with an info button above the conditions that toggles
a reference-only panel. The panel lists every registered placeholder
with its label, literal token, and description in a responsive grid, and
closes from the button or its own close control. It is hidden by default
and its state resets each time the modal opens.

Remove the insert plumbing this makes redundant: the focused-condition
tracking, the insert notice, and the onValueFocus prop threaded through
the condition list and card.
Users pick placeholders by label from the Meta Value field and only see
the {aql:} form in the saved markup, so listing it added noise.
@ryanwelcher
ryanwelcher added this pull request to stack #183 September 10, 2026 15:18
Split the placeholder reference into three PanelBody sections, all
collapsed when the panel opens: Content & users, Current date & time,
and Relative dates. Each title carries its item count. The grouping is
an editor-only lookup keyed by placeholder name; anything not in it,
such as third-party placeholders, falls into an Other section that
renders only when it has entries. No PHP or API changes.
Move the info button into the sticky footer, right-aligned opposite the
Add/Reset actions, and show the reference in a Popover anchored to it.
The popover floats over the conditions instead of pushing them down,
and closes on Escape, an outside click, or its own close button without
closing the modal.
…at a time

Anchor the popover below the modal header, centered, with flipping off
so its top edge stays put and it grows downward as sections expand. The
grouped sections are now controlled and behave as an accordion: opening
one closes the others.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

There are a couple of confirmed runtime/API issues in the new modal builder code paths (spreading possibly-undefined meta_query and passing unsupported props to DatePicker) that should be fixed before merge.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR moves the Post Meta Query builder UI into a dedicated modal and reworks the meta-query editing experience (condition cards, advanced controls, placeholder reference popover), while extending server-side meta query generation (and PHPUnit coverage) to support nested meta query groups.

Changes:

  • Introduces a reusable QueryBuilderModal shell and migrates the meta query builder into it with a sticky footer.
  • Adds a meta-query entry tree model (conditions + nested groups) and a recursive UI renderer for cards/groups, plus a placeholder reference popover.
  • Updates the PHP Meta_Query trait to recursively translate nested groups into WP_Query-compatible meta_query clauses and adds unit/e2e coverage for the new behavior.
File summaries
File Description
tests/unit/Meta_Query_Tests.php Adds PHPUnit coverage for nested meta_query groups, empty-group dropping, and parity cases.
tests/e2e/tests/meta-query-modal.spec.ts Adds Playwright coverage for the modal UI, cards, placeholder reference, operator/type guidance, and groups.
src/variations/editor.scss Adds styling for modal layout, condition cards/groups, placeholder reference popover, and token-field suggestion overlay behavior.
src/utils/meta-query-tree.js Introduces utilities for representing and manipulating a condition/group meta-query tree.
src/components/query-builder-modal.js Adds the generic modal shell wrapper for query builders.
src/components/post-meta-query-controls.js Replaces the flyout dropdown meta builder with the modal-based builder and footer actions.
src/components/post-meta-control.js Refactors a single condition into a “card” UI with Advanced mode, operator gating, and date picker integration.
src/components/placeholder-text-control.js Adds optional onFocus and help plumbing to support improved guidance in the modal.
src/components/placeholder-reference.js Adds the placeholder reference-only popover panel UI and grouping.
src/components/meta-value-picker.js Adds inline DATE/DATETIME/TIME pickers that write values in WP_Query-compatible formats.
src/components/meta-condition-list.js Adds recursive renderer for conditions and one-level nested groups with relation toggles.
includes/Traits/Meta_Query.php Adds recursive parsing for nested group entries into WP_Query meta_query clauses.
Review details

Suppressed comments (1)

src/components/post-meta-query-controls.js:82

  • attributes.query.meta_query can be undefined initially, so spreading it here can throw before any meta query has been created. Use a nullish fallback when spreading.
				meta_query: {
					...attributes.query.meta_query,
					queries: newQueries,
				},
  • Files reviewed: 12/12 changed files
  • Comments generated: 4
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/components/post-meta-query-controls.js
Comment thread src/components/meta-value-picker.js Outdated
Comment thread src/components/post-meta-control.js
Comment on lines +97 to +101
{ items.map( ( { name, label, description } ) => (
<div key={ name } className="aql-placeholder-reference__item">
<dt className="aql-placeholder-reference__label">{ label }</dt>
{ description && <dd>{ description }</dd> }
</div>
… open

The accordion assertions added in c266b94 collapse the Content & users
group before the older assertion that expects that group's description
text to be visible, so the test contradicted itself and failed on CI.
Render the DATE, DATETIME and TIME pickers explicitly so is12Hour is only
passed to the pickers that accept it, and store an empty string instead
of undefined when the Meta Key token field is cleared.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

There is a confirmed runtime-crash risk in the updated meta query controls when meta_query is absent (e.g., after reset) due to spreading attributes.query.meta_query without a null-safe fallback.

Review details

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

tests/e2e/tests/meta-query-modal.spec.ts:671

  • Fixed waitForTimeout sleeps tend to make Playwright specs flaky across environments; it’s more reliable to wait on a specific editor state (e.g. poll until the derived ID set reaches the expected length).

src/components/post-meta-query-controls.js:67

  • attributes.query.meta_query can be undefined (e.g. after MetaQueryGroupControls deletes it on reset), so spreading it here will throw when adding conditions or changing relation. Use a nullish-coalesced spread so the builder works from a clean slate.
	const setRelation = ( newRelation ) => {
		setAttributes( {
			query: {
				...attributes.query,
				meta_query: {
  • Files reviewed: 12/12 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

…d query e2e test

Spreading an undefined meta_query is legal, but the nullish fallback
makes the clean-slate case explicit. The nested query test now polls the
editor preview for the expected post count instead of a fixed sleep, and
the ten-condition layout test is marked slow for constrained runners.
The test builds three conditions, publishes and loads the frontend, which
ran past Playwright's 30s default on a slow runner. Once the test timed
out, the fixture tore Playground down, so the frontend request surfaced
as a connection refusal rather than the timeout.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow nested meta_query with relation setting

2 participants