Skip to content

feat: per-post layout overrides on Query Loop (variant system) - #598

Merged
ViewFromTheBox merged 2 commits into
release/1.2from
feature/591-query-loop-post-variants
Jun 16, 2026
Merged

ViewFromTheBox merged 2 commits into
release/1.2from
feature/591-query-loop-post-variants

Conversation

@ViewFromTheBox

@ViewFromTheBox ViewFromTheBox commented Jun 16, 2026 •

Copy link
Copy Markdown
Member

Description

Adds a variant system to the Query Loop block so authors can override the post template for specific posts in the loop — the first post as a hero card, odd / even alternation, sticky posts in a distinct layout, etc. Closes the gap left by Gutenberg's single-template Query Loop without forcing authors back into PHP.

Closes: #591

Type of Change

  • New feature (adds new functionality)

Related Issue

Issue: #591

Motivation and Context

Pre-Gutenberg PHP gave full control via the_loop() + CSS :nth-child. Gutenberg's Query Loop renders every post with the same Post Template. This PR closes that gap inside the visual editor.

Changes Made

  • New block artisanpack/post-variant (parent-locked to artisanpack/post-template) with attributes matcher, priority, label. Inspector exposes matcher kind, preset picker, raw value field, priority slider.
  • "Post Variants" panel in the Query Loop inspector — list / select / reorder / delete plus add buttons for each of the four kinds. Writes a precompiled position → variantOrder map onto the parent post-template's new _compiledVariantMap attribute.
  • Shared matcher engine (resources/js/visual-editor/editor/variant-matcher.ts) — pure TS spec consumed by save-time compile + canvas. 12 unit tests pass.
  • PHP VariantResolver (src/Resources/VariantResolver.php) — server-side counterpart. Precedence: instance > position > pattern > meta > custom > base, ties on priority ascending then document order. Implements meta (sticky / featured / has-featured-image / author / taxonomy) and custom (via apve_query_variant_match_<name> filter hook).
  • QueryInliner integration — strips variants from the iteration template, primes the resolver, swaps the iteration template per post. Items rendered via a variant carry an is-variant class on their core/post-template-item wrapper.
  • Renderer parity — artisanpack/post-variant registered in Blade (recursive include), React (PostVariantBlock), Vue (PostVariantBlock); 152/152 renderer-parity check ✅.
  • Docs: new docs/blocks/Post-Variants.md + apve_query_variant_match_<name> entry in docs/Hooks-and-Events.md; CHANGELOG.md updated.

How Has This Been Tested?

Testing Environment:

  • Operating System: macOS
  • Browser: Safari (artisanpack-ui-dev Herd site)
  • PHP Version: 8.4
  • Project Version: 1.2-dev (base branch release/1.2)

Tests Performed:

  1. Manual: insert Query Loop in the dev app → confirm "Post Variants" panel appears, no React crashes (the initial useEffect write loop was fixed pre-commit by comparing the compiled map against the stored signature before dispatching).
  2. Manual: add position / pattern / meta variants → confirm each renders the right template on the front end; base posts render the base template; is-variant class lands on the right <li>.
  3. Manual: confirmed Query Loops with no variants render byte-identical to before (regression check).
  4. Automated: new QueryInlinerVariantsTest (8 tests, 21 assertions) and variant-matcher.test.ts (12 tests). All 15 pre-existing QueryInlinerTest cases still pass.

Accessibility Tests Run

  • Keyboard navigation tested
  • Screen reader tested
  • Color contrast verified
  • ARIA labels checked

Details: Panel action buttons (↑, ↓, ×) carry aria-labels. The select-variant button is a real <button> so keyboard activates the canvas selection via selectBlock. Block-editor inspector chrome is unchanged.

Tests Added

  • Unit tests added/updated
  • Integration tests added/updated
  • All tests passing

Test details:

  • tests/Unit/VisualEditor/Resources/QueryInlinerVariantsTest.php — backward compat (no variants = unchanged), position swap, pattern (odd / even), meta:sticky walk, custom callback via apve_query_variant_match_<name>, precedence cascade, precompiled map fast-path, priority tie-break.
  • resources/js/visual-editor/editor/__tests__/variant-matcher.test.ts — matchPosition, matchPattern, matchMeta, sortVariants precedence + tie-break, compileStaticMap, resolveVariant.

Documentation

  • Inline code documentation added
  • Wiki updated

Documentation details: docs/blocks/Post-Variants.md covers the block, four matcher kinds, the fixed precedence cascade, editor UX, hybrid resolution, custom matchers, and backward-compat guarantees. docs/Hooks-and-Events.md adds the apve_query_variant_match_<name> filter section. CHANGELOG.md carries the user-facing summary under [Unreleased].

Pre-Submission Checklist

  • Followed contributing guidelines
  • Checked for other open PRs for same update
  • Code passes all tests
  • Code has been linted (CodeRabbit CLI run pre-push against release/1.2; all findings addressed)
  • Accessibility tests completed (keyboard + ARIA — see above)
  • Code follows project style guide
  • Self-review completed
  • Comments added for complex code
  • No new warnings generated

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added per-post layout overrides in query loops via post variants, with fixed precedence and priority-based tie-breaking.
    • Introduced a Post Variants inspector panel for creating, reordering, and configuring variants.
    • Added renderer support so variants behave consistently across editor previews and server rendering, including variant styling markers.
  • Documentation

    • Documented post-variant usage and the apve_query_variant_match_<name> custom matcher filter.
  • Tests

    • Added automated coverage for matcher behavior, precedence, and compiled-map fast paths.

Adds the artisanpack/post-variant block — a child of
artisanpack/post-template that declares an override template applied
to posts matching its rule. Closes the gap left by Gutenberg's
single-template Query Loop: authors can now make the first post a
hero card while the rest render as a compact list, alternate odd /
even styles, give sticky / featured posts their own layout, or wire
custom logic via a PHP filter — all without leaving the editor.

V1 ships all four matcher kinds (position / pattern / meta / custom),
the fixed precedence cascade (instance > position > pattern > meta >
custom > base) with priority ascending as the tie-breaker, a "Post
Variants" panel in the query inspector, save-time precompilation of
static rules to a position->variantOrder map on the parent
post-template, and an apve_query_variant_match_<name> filter hook
for extending the matcher set without forking the package.

Server-side resolution lives in QueryInliner / the new
VariantResolver, so the Blade, React, and Vue renderers all consume
the same expanded tree — no per-renderer iteration logic to drift.
Variants are stripped from the rendered tree on the way out, and
items rendered via a variant pick up an extra is-variant class on
their core/post-template-item wrapper for downstream styling. Existing
query loops with no variants render identically to before.

Closes #591

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 16, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8cbf9219-cabe-47bd-8f19-4c889d66774f

📥 Commits

Reviewing files that changed from the base of the PR and between d149220 and cea97ca.

📒 Files selected for processing (5)
  • resources/js/visual-editor/blocks/query/post-variants-panel.tsx
  • resources/js/visual-editor/editor/__tests__/variant-matcher.test.ts
  • resources/js/visual-editor/editor/variant-matcher.ts
  • src/Resources/VariantResolver.php
  • tests/Unit/VisualEditor/Resources/QueryInlinerVariantsTest.php
🚧 Files skipped from review as they are similar to previous changes (3)
  • resources/js/visual-editor/blocks/query/post-variants-panel.tsx
  • src/Resources/VariantResolver.php
  • tests/Unit/VisualEditor/Resources/QueryInlinerVariantsTest.php

📝 Walkthrough

Walkthrough

Introduces the artisanpack/post-variant block as a child of artisanpack/post-template, enabling per-post layout overrides in the Query Loop. Adds a client-side matcher engine, a Query inspector panel, a PHP VariantResolver, QueryInliner integration for hybrid static/dynamic resolution, cross-renderer pass-through components (Blade/React/Vue), and comprehensive tests and documentation.

Changes

Query Loop Post-Variant System

Layer / File(s) Summary
Client-side matcher engine
resources/js/visual-editor/editor/variant-matcher.ts, resources/js/visual-editor/editor/__tests__/variant-matcher.test.ts
Defines all matcher types and interfaces (MatcherKind, PositionMatcher, PatternMatcher, MetaMatcher, CustomMatcher, VariantDescriptor), implements parsing and matching utilities, sortVariants, compileStaticMap, and resolveVariant with full Vitest coverage.
post-variant block definition, edit, save, and entrypoint
resources/js/visual-editor/blocks/post-variant/block.json, resources/js/visual-editor/blocks/post-variant/edit.tsx, resources/js/visual-editor/blocks/post-variant/save.tsx, resources/js/visual-editor/blocks/post-variant/inserter-icon.tsx, resources/js/visual-editor/blocks/post-variant/index.ts, resources/js/visual-editor/blocks/post-template/block.json
Registers artisanpack/post-variant with matcher/priority/label attributes, inspector controls for all matcher kinds, InnerBlocks save/edit round-trip, SVG icon, and adds _compiledVariantMap attribute to post-template.
PostVariantsPanel inspector component
resources/js/visual-editor/blocks/query/post-variants-panel.tsx, resources/js/visual-editor/blocks/query/post-variants-panel.css, resources/js/visual-editor/blocks/query/edit.tsx
Enumerates post-variant children via useSelect, compiles the static map via useMemo, writes _compiledVariantMap back onto post-template with a guarded useEffect, and provides add/move/delete/select UI actions with scoped CSS styles. Wired into QueryEdit InspectorControls.
PHP VariantResolver class
src/Resources/VariantResolver.php
Server-side resolver mirroring editor-side precedence: prime() normalizes variants and the compiled map; resolve() uses the O(1) static map or falls back to sorted tier evaluation; matcher methods cover position, pattern, meta (sticky/featured/taxonomy/author), and custom via applyFilters.
QueryInliner variant integration
src/Resources/QueryInliner.php
Accepts optional VariantResolver in constructor; adds extractVariants() to split the base template from variant blocks; primes the resolver per expandQuery() call; selects base or variant inner blocks per loop iteration; conditionally appends is-variant to the post-template-item className.
Cross-renderer registration and service provider wiring
packages/visual-editor-renderer-react/src/blocks/core/query.tsx, packages/visual-editor-renderer-react/src/blocks/registerCoreBlocks.ts, packages/visual-editor-renderer-vue/src/blocks/core/query.ts, packages/visual-editor-renderer-vue/src/blocks/registerCoreBlocks.ts, packages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/post-variant.blade.php, packages/renderer-parity.json, src/VisualEditorServiceProvider.php
Registers PostVariantBlock pass-through renderers in React, Vue, and Blade; adds artisanpack/post-variant to renderer-parity.json; registers the block in VisualEditorServiceProvider forked blocks list.
PHP integration tests and documentation
tests/Unit/VisualEditor/Resources/QueryInlinerVariantsTest.php, CHANGELOG.md, docs/blocks/Post-Variants.md, docs/Hooks-and-Events.md
Adds QueryInlinerVariantsTest covering all matcher kinds, precedence cascade, compiled-map fast path, tie-breaking, and backward compatibility. Documents the feature in Post-Variants.md and the apve_query_variant_match_<name> filter in Hooks-and-Events.md.

Sequence Diagram(s)

sequenceDiagram
    participant Editor as Block Editor
    participant PostVariantsPanel
    participant BlockEditorStore as core/block-editor
    participant QueryInliner
    participant VariantResolver

    Editor->>PostVariantsPanel: render(queryClientId, previewTotal)
    PostVariantsPanel->>BlockEditorStore: useSelect(post-template + post-variant children)
    BlockEditorStore-->>PostVariantsPanel: variantBlocks[]
    PostVariantsPanel->>PostVariantsPanel: compileStaticMap(variants, previewTotal)
    PostVariantsPanel->>BlockEditorStore: updateBlockAttributes(_compiledVariantMap)

    Note over QueryInliner,VariantResolver: Server-side render path
    QueryInliner->>QueryInliner: extractVariants(post-template innerBlocks)
    QueryInliner->>VariantResolver: prime(variantBlocks, compiledMap, total)
    loop Each post in query results
        QueryInliner->>VariantResolver: resolve(index, post)
        VariantResolver-->>QueryInliner: variantOrder or null
        QueryInliner->>VariantResolver: innerBlocksFor(variantOrder, variants)
        VariantResolver-->>QueryInliner: activeInnerBlocks
        QueryInliner->>QueryInliner: stamp post-template-item + append is-variant class
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • ArtisanPack-UI/visual-editor#505: Both PRs operate on the QueryInliner's core/post-template/core/post-template-item expansion pipeline; #505 introduced the post-template-item wrapper structure that this PR extends by stamping is-variant and swapping innerBlocks per iteration.

Poem

🐇 Hop, hop, the loop now knows each post by name,
First one's a hero, odd ones play a different game.
The matcher engine sorts by tier and priority,
Static maps compiled, O(1) with clarity.
is-variant stamped, the Blade and Vue and React agree —
A rabbit built this loop, and every post runs free! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: per-post layout overrides on Query Loop (variant system)' clearly and concisely summarizes the main feature introduced: a variant system enabling per-post layout customization in Query Loops.
Linked Issues check ✅ Passed The implementation fully satisfies all major coding requirements from issue #591: the new artisanpack/post-variant block with matcher/priority/label attributes, all four matcher kinds (position, pattern, meta, custom), correct precedence resolution (instance > position > pattern > meta > custom > base), hybrid save-time/runtime evaluation, parity across Blade/React/Vue renderers, extensibility via apve_query_variant_match_<name> filter hook, backward compatibility guarantees, and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #591 objectives: new blocks, matcher engine, resolver implementations, editor panels, test suites, and documentation. No unrelated refactoring, dependency updates, or out-of-scope modifications are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/591-query-loop-post-variants

Comment @coderabbitai help to get the list of available commands and usage tips.

@ViewFromTheBox ViewFromTheBox moved this from Backlog to In Review in ArtisanPack UI Overview Jun 16, 2026
@ViewFromTheBox
ViewFromTheBox marked this pull request as ready for review June 16, 2026 22:29
@ViewFromTheBox ViewFromTheBox linked an issue Jun 16, 2026 that may be closed by this pull request
1 of 15 tasks

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/Resources/VariantResolver.php (1)

102-127: 💤 Low value

Consider caching the sorted variants array to avoid repeated sorting.

The usort() call runs on every resolve() invocation when the compiled map doesn't have an entry. For queries with many results where meta/custom matchers are in play, this sorts the same array repeatedly. Since prime() is called once per query expansion, the sorted array could be computed and stored there.

This is a minor optimization since: (1) the compiled map short-circuits most iterations for well-formed saves, and (2) the variant count is typically small.

♻️ Proposed optimization
+    /**
+     * `@var` array<int, array<string, mixed>>
+     */
+    protected array $sortedVariants = [];

     public function prime( array $variants, array $compiledMap, int $total ): void
     {
         $this->variants    = $this->describeVariants( $variants );
         $this->compiledMap = $this->normalizeMap( $compiledMap );
         $this->total       = $total;
+
+        $this->sortedVariants = $this->variants;
+        usort( $this->sortedVariants, function ( array $a, array $b ): int {
+            $tierDiff = $a['tier'] - $b['tier'];
+            if ( 0 !== $tierDiff ) {
+                return $tierDiff;
+            }
+            $priorityDiff = $a['priority'] - $b['priority'];
+            if ( 0 !== $priorityDiff ) {
+                return $priorityDiff;
+            }
+            return $a['order'] - $b['order'];
+        } );
     }

     public function resolve( int $index, object $post ): ?int
     {
         if ( isset( $this->compiledMap[ $index ] ) ) {
             $mapped = $this->compiledMap[ $index ];
             if ( $mapped >= 0 && $mapped < count( $this->variants ) ) {
                 return $mapped;
             }
         }

-        $sorted = $this->variants;
-        usort( $sorted, function ( array $a, array $b ): int {
-            $tierDiff = $a['tier'] - $b['tier'];
-            if ( 0 !== $tierDiff ) {
-                return $tierDiff;
-            }
-            $priorityDiff = $a['priority'] - $b['priority'];
-            if ( 0 !== $priorityDiff ) {
-                return $priorityDiff;
-            }
-            return $a['order'] - $b['order'];
-        } );
-
-        foreach ( $sorted as $variant ) {
+        foreach ( $this->sortedVariants as $variant ) {
             if ( $this->matches( $variant, $index, $post ) ) {
                 return $variant['order'];
             }
         }

         return null;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Resources/VariantResolver.php` around lines 102 - 127, The resolve()
method performs usort() on the same variants array on every invocation, which is
inefficient when resolve() is called multiple times. To fix this, move the
sorting logic from resolve() to the prime() method where it will execute only
once per query expansion, and store the sorted result in an instance variable
(e.g., $this->sortedVariants). Then in resolve(), use this pre-sorted array
instead of calling usort() each time. This eliminates the repeated sorting while
keeping the sorting logic in a logical place where initialization happens.
resources/js/visual-editor/editor/__tests__/variant-matcher.test.ts (1)

93-122: ⚡ Quick win

Add a zero-total regression case for static-map compilation

A dedicated total = 0 assertion would lock expected behavior and catch map-generation regressions around empty/loading previews.

Suggested test
 describe( 'compileStaticMap', () => {
+    it( 'returns an empty map when total is 0', () => {
+        const variants: VariantDescriptor[] = [
+            { order: 0, priority: 10, matcher: { kind: 'position', value: 'last' } },
+            { order: 1, priority: 10, matcher: { kind: 'pattern', value: 'odd' } },
+        ];
+        expect( compileStaticMap( variants, 0 ) ).toEqual( {} );
+    } );
+
     it( 'compiles position + pattern rules into an index → order map', () => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@resources/js/visual-editor/editor/__tests__/variant-matcher.test.ts` around
lines 93 - 122, Add a new test case inside the `compileStaticMap` describe block
that tests the edge case where `total = 0` is passed to the compileStaticMap
function. This test should verify the expected behavior when handling empty or
loading previews (e.g., when no items exist yet). Create a simple variant
descriptor array with at least one variant and call compileStaticMap with a
total of 0, then assert what the resulting map should be to establish the
baseline behavior and catch regressions in this edge case scenario.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@resources/js/visual-editor/blocks/query/post-variants-panel.tsx`:
- Around line 156-160: The Math.max(previewTotal, 1) call on the line assigning
the total variable artificially inflates the total to 1 when previewTotal is 0,
causing compileStaticMap to incorrectly compile position-dependent rules like
position:last as if there is at least one item. Remove the Math.max wrapper and
pass previewTotal directly as the total parameter to compileStaticMap so that
the compiled variant map accurately reflects the actual preview count, including
when it is zero.

In `@resources/js/visual-editor/editor/variant-matcher.ts`:
- Around line 262-329: The compileStaticMap function bakes position-dependent
matches (like position:last) at save time based on a specific total value, and
resolveVariant trusts static map hits without re-validating them. When the post
count changes, these cached matches become stale. To fix this, in
compileStaticMap exclude position and pattern matchers from the static map (keep
only instance-based matches that are position-independent), and in
resolveVariant add fallback evaluation logic that checks position and pattern
matchers against the current total value when the static map misses (using the
same matchPosition and matchPattern helper functions already used in
compileStaticMap). This ensures dynamic queries always evaluate position rules
against the current total rather than a stale saved value.

In `@tests/Unit/VisualEditor/Resources/QueryInlinerVariantsTest.php`:
- Around line 174-193: The test registers a filter with addFilter for
'apve_query_variant_match_premium' but if an assertion throws an exception
before reaching removeAllFilters, the cleanup won't execute and the filter will
leak into subsequent tests. Wrap the filter registration and all subsequent test
code (including the variant block setup, inlining, and assertions) in a
try/finally block, placing the removeAllFilters call inside the finally block to
guarantee cleanup always runs regardless of whether assertions pass or fail.

---

Nitpick comments:
In `@resources/js/visual-editor/editor/__tests__/variant-matcher.test.ts`:
- Around line 93-122: Add a new test case inside the `compileStaticMap` describe
block that tests the edge case where `total = 0` is passed to the
compileStaticMap function. This test should verify the expected behavior when
handling empty or loading previews (e.g., when no items exist yet). Create a
simple variant descriptor array with at least one variant and call
compileStaticMap with a total of 0, then assert what the resulting map should be
to establish the baseline behavior and catch regressions in this edge case
scenario.

In `@src/Resources/VariantResolver.php`:
- Around line 102-127: The resolve() method performs usort() on the same
variants array on every invocation, which is inefficient when resolve() is
called multiple times. To fix this, move the sorting logic from resolve() to the
prime() method where it will execute only once per query expansion, and store
the sorted result in an instance variable (e.g., $this->sortedVariants). Then in
resolve(), use this pre-sorted array instead of calling usort() each time. This
eliminates the repeated sorting while keeping the sorting logic in a logical
place where initialization happens.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 89b9f322-38b8-4102-baae-ee18ae3c1461

📥 Commits

Reviewing files that changed from the base of the PR and between e11a33b and d149220.

📒 Files selected for processing (24)
  • CHANGELOG.md
  • docs/Hooks-and-Events.md
  • docs/blocks/Post-Variants.md
  • packages/renderer-parity.json
  • packages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/post-variant.blade.php
  • packages/visual-editor-renderer-react/src/blocks/core/query.tsx
  • packages/visual-editor-renderer-react/src/blocks/registerCoreBlocks.ts
  • packages/visual-editor-renderer-vue/src/blocks/core/query.ts
  • packages/visual-editor-renderer-vue/src/blocks/registerCoreBlocks.ts
  • resources/js/visual-editor/blocks/post-template/block.json
  • resources/js/visual-editor/blocks/post-variant/block.json
  • resources/js/visual-editor/blocks/post-variant/edit.tsx
  • resources/js/visual-editor/blocks/post-variant/index.ts
  • resources/js/visual-editor/blocks/post-variant/inserter-icon.tsx
  • resources/js/visual-editor/blocks/post-variant/save.tsx
  • resources/js/visual-editor/blocks/query/edit.tsx
  • resources/js/visual-editor/blocks/query/post-variants-panel.css
  • resources/js/visual-editor/blocks/query/post-variants-panel.tsx
  • resources/js/visual-editor/editor/__tests__/variant-matcher.test.ts
  • resources/js/visual-editor/editor/variant-matcher.ts
  • src/Resources/QueryInliner.php
  • src/Resources/VariantResolver.php
  • src/VisualEditorServiceProvider.php
  • tests/Unit/VisualEditor/Resources/QueryInlinerVariantsTest.php

Comment thread resources/js/visual-editor/blocks/query/post-variants-panel.tsx Outdated
Comment thread resources/js/visual-editor/editor/variant-matcher.ts
Comment thread tests/Unit/VisualEditor/Resources/QueryInlinerVariantsTest.php
- Variant matcher: drop position / pattern from compileStaticMap and
  evaluate them at render time instead. The baked map carried
  position-dependent results (notably `position:last`) keyed off the
  editor preview total — when the live query returned a different
  count, those entries pointed at the wrong index. The static map is
  now narrowly scoped to `instance:<n1>` pins, which are fixed by
  definition. resolveVariant walks position / pattern / meta at
  render time with the live total.
- Panel: drop the Math.max(previewTotal, 1) inflation — previewTotal
  flows through verbatim so the map honestly reflects an empty /
  loading preview.
- VariantResolver: cache the sorted variants array in prime() instead
  of resorting on every resolve() call.
- Tests: add a total=0 regression for compileStaticMap, add
  render-time evaluation cases for position / pattern / precedence in
  resolveVariant, wrap the custom-filter test in try/finally so
  apve_query_variant_match_premium cleanup runs even on assertion
  failure.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@ViewFromTheBox
ViewFromTheBox merged commit a7f8a15 into release/1.2 Jun 16, 2026
6 checks passed
@github-project-automation github-project-automation Bot moved this from In Review to Done in ArtisanPack UI Overview Jun 16, 2026
@ViewFromTheBox
ViewFromTheBox deleted the feature/591-query-loop-post-variants branch June 16, 2026 22:47
@ViewFromTheBox ViewFromTheBox mentioned this pull request Jun 18, 2026
13 of 14 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

Feature: Advanced per-post layout overrides for Query Loop (variant system)

1 participant