Skip to content

feat: variable column/row spans for posts in Query Loop grid layout - #602

Merged
ViewFromTheBox merged 2 commits into
release/1.2from
enhancement/592-variable-grid-spans-on-post-variants
Jun 18, 2026
Merged

ViewFromTheBox merged 2 commits into
release/1.2from
enhancement/592-variable-grid-spans-on-post-variants

Conversation

@ViewFromTheBox

@ViewFromTheBox ViewFromTheBox commented Jun 18, 2026 •

Copy link
Copy Markdown
Member

Description

Adds gridColumnSpan / gridRowSpan attributes (default 1) to artisanpack/post-variant, both opting into the artisanpackResponsive cascade for per-breakpoint values. When the parent Query Loop's post-template renders in grid layout and a post matches a variant, the spans propagate to the iteration <li> so authors can build magazine-style layouts (e.g. lead story spans 2×2, sticky posts span 2×1) without leaving the post-variant system from #591.

Closes: #592

Type of Change

  • New feature (adds new functionality)
  • Enhancement (improves existing functionality)

Related Issue

Issue: #592

Motivation and Context

Before this change every post in a Query Loop's grid layout occupied a 1×1 cell — there was no way to say "make the first post 2×2 and the second post 2×1." The standalone artisanpack/grid block already supports the same expressiveness via artisanpack/grid-item. This issue brings parity to the Query Loop, keeping a single mental model: any per-post difference is just a variant attribute, and spans are one more thing a variant can change.

Changes Made

  • Block schema: artisanpack/post-variant declares gridColumnSpan / gridRowSpan (default 1) and lists both in supports.artisanpackResponsive.attributes.
  • Context plumbing: post-template now providesContext for its layout / columns attributes under artisanpack/postTemplateLayout / Columns so the variant inspector can gate the new "Grid Spans" panel on the parent template's grid state and clamp the column slider to its column count.
  • Editor inspector: new "Grid Spans" PanelBody on the post-variant block, visible only when the parent post-template is in grid layout. Column span clamps to 1..numColumns; row span clamps to 1..12 with a notice above 4.
  • Layout parity: post-template editor switched from upstream is-flex-container to is-layout-grid so the editor canvas and Blade frontend share the same CSS-grid layout. New post-template.css defines grid-template-columns: repeat(N, …) for is-layout-grid.columns-N (the layout baseline only ships display: grid).
  • Server pipeline: QueryInliner stamps a normalised _resolvedGridSpan = { columns, rows } on the synthetic core/post-template-item wrapper when (a) a variant matched and (b) the post-template's layout === 'grid'. Defaults of 1×1 with no breakpoint overrides skip the stamp entirely.
  • Renderers: Blade / Vue / React PostTemplateItem translate _resolvedGridSpan into ap-post-span-N-{bp}-{columns,row} classes via a shared postTemplateItemSpanClasses() helper (Blade side gates on the runtime BreakpointRegistry).
  • CSS: post-variant.css carries the span rules with two parallel selectors per breakpoint — a direct compound match for the frontend <li.wp-block-post-template-item> and a :has()-based match for the editor canvas's <li.wp-block-post> (where the span class is nested on the variant wrapper, not on the <li> itself).
  • Asset wiring: BlocksStylesComponent ships post-template.css and post-variant.css as new linked stylesheets, with helpful comments above each <link> explaining the role.

How Has This Been Tested?

Testing Environment:

  • Operating System: macOS 25.5.0 (Darwin)
  • Browser (if applicable): Chrome (canvas + frontend exercised by hand)
  • PHP Version: 8.4.3
  • Project Version: branched from release/1.2

Tests Performed:

  1. Manual: created a Query Loop in ~/Herd/artisanpack-ui-dev, switched the post-template to grid, added a position:first post-variant, set column span 2 / row span 2. Verified the variant's inspector showed the new Grid Spans panel, that it disappeared when toggling the post-template back to list, that the column slider capped at the parent's column count, and that both editor canvas and rendered frontend showed the first post spanning 2×2 across a 3-column grid.
  2. PHP: ./vendor/bin/pest --compact → 1315 passed, 1 skipped (pre-existing hooks skip).
  3. JS: npx vitest run → 2223 passed, 258 files.
  4. Renderer parity: npm run verify:parity → clean.
  5. CodeRabbit CLI (cr review --base release/1.2 --agent) — 1 minor finding (request to add JSON-schema min/max to block attribute definitions); skipped to stay consistent with the existing artisanpack/grid-item attribute precedent, given that clamping is already enforced in the editor, the inliner, and the renderers. Second pass returned 0 findings.

Accessibility Tests Run

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

Details:

Inspector controls are standard @wordpress/components RangeControls, which are already keyboard-navigable and screen-reader-labelled. No new custom UI was introduced. The visible markup change is purely class-based — the rendered semantics (<ul> + <li> with id="post-N") are unchanged from the existing post-template family.

Tests Added

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

Test details:

  • tests/Unit/VisualEditor/Resources/QueryInlinerVariantSpansTest.php — 6 cases covering the stamp gating (grid layout only), the omitted-stamp shortcut for 1×1 defaults, base + responsive merging, clamping to 1..12, and the variant precedence cascade for spans.
  • packages/visual-editor-renderer-blade/tests/Feature/PostVariantGridSpansRenderTest.php — 3 end-to-end render tests asserting the right class set on the rendered <li> (or its absence on non-grid layouts), including per-breakpoint overrides.
  • resources/js/visual-editor/blocks/__tests__/post-variant-grid-spans.test.ts — 9 cases pinning the block.json shape and renderer-parity helper output between Vue and React.

Documentation

  • Inline code documentation added
  • README updated
  • Wiki updated
  • API documentation updated

Documentation details:

Each new file carries a header docblock explaining its role (post-template grid container vs. post-variant span overrides; the two-selector pattern for editor / frontend markup). Resource-level PHPDoc on QueryInliner::resolveVariantSpans() documents the normalisation rules and the 1×1 short-circuit.

Screenshots

See acceptance demo on the artisanpack-ui-dev app — a position:first variant set to 2 cols × 2 rows in a 3-column grid renders as the lead cell across both editor canvas and frontend output.

Pre-Submission Checklist

  • Followed contributing guidelines
  • Checked for other open PRs for same update
  • Code passes all tests
  • Code has been linted (no .php-cs-fixer config at package level — manual style matched)
  • Accessibility tests completed (see above)
  • Code follows project style guide
  • Self-review completed
  • Comments added for complex code
  • No new warnings generated

Summary by CodeRabbit

  • New Features
    • Added responsive grid styling for post templates (supports 1–6 columns) and reset behavior for grid items.
    • Added responsive grid span controls to post variants, enabling per-breakpoint column and row spanning when the post template is in grid layout.
  • Enhancements
    • Post-template items now apply span-aware CSS classes across Blade, React, and Vue renderers, with validation and clamping to supported ranges.
    • Updated editor UI and block context to drive grid-span rendering consistently.
  • Tests
    • Added end-to-end and unit test coverage for responsive grid span class emissions and variant precedence.

Add `gridColumnSpan` / `gridRowSpan` attributes (default 1) to the
`artisanpack/post-variant` block. Both opt into the `artisanpackResponsive`
cascade so authors can set per-breakpoint spans. When the parent Query
Loop's post-template renders in grid layout and a post matches a
variant, the spans propagate to the iteration `<li>` via
`ap-post-span-N-{bp}-{columns,row}` classes.

- Post-template now provides its `layout` / `columns` attributes as
  block context (`artisanpack/postTemplateLayout` / `Columns`) so the
  variant inspector can gate the new "Grid Spans" panel on the parent
  template's grid state and clamp the column slider to its column count.
- Post-template editor switched from upstream `is-flex-container` to
  `is-layout-grid` so the editor canvas and Blade frontend share the
  same CSS-grid layout — spans now visibly take effect in both.
- New `post-template.css` defines `grid-template-columns: repeat(N, …)`
  for `is-layout-grid.columns-N` (the layout baseline only ships
  `display: grid`).
- `QueryInliner` stamps a normalised `_resolvedGridSpan = { columns,
  rows }` on the synthetic `core/post-template-item` wrapper when (a) a
  variant matched and (b) the post-template's `layout === 'grid'`. The
  Blade / Vue / React renderers translate that into the class list via
  a shared `postTemplateItemSpanClasses()` helper plus a Blade-side
  emitter gated on the runtime `BreakpointRegistry`.
- `post-variant.css` carries the span rules with two parallel
  selectors per breakpoint: a direct compound match for the frontend
  `<li.wp-block-post-template-item>` and a `:has()`-based match for
  the editor canvas's `<li.wp-block-post>` (where the span class is
  nested on the variant block wrapper, not on the `<li>` itself).
- Tests: Pest covers the inliner (precedence, clamping, grid-gating,
  responsive merging), a Blade feature test asserts end-to-end class
  emission, and a vitest suite pins renderer parity between Vue and
  React span-class helpers.

Closes #592

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

coderabbitai Bot commented Jun 18, 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: 8a3ed4d8-5ab8-44dc-af68-cb033006a634

📥 Commits

Reviewing files that changed from the base of the PR and between c6f6014 and 9237e41.

📒 Files selected for processing (3)
  • resources/js/visual-editor/blocks/post-variant/edit.tsx
  • src/Resources/QueryInliner.php
  • tests/Unit/VisualEditor/Resources/QueryInlinerVariantSpansTest.php
🚧 Files skipped from review as they are similar to previous changes (3)
  • resources/js/visual-editor/blocks/post-variant/edit.tsx
  • src/Resources/QueryInliner.php
  • tests/Unit/VisualEditor/Resources/QueryInlinerVariantSpansTest.php

📝 Walkthrough

Walkthrough

Adds responsive gridColumnSpan and gridRowSpan attributes to the artisanpack/post-variant block. The core/post-template block now provides layout context to child blocks. QueryInliner stamps _resolvedGridSpan on each inlined core/post-template-item when a grid layout is active. React, Vue, and Blade renderers consume this attribute to emit ap-post-span-* CSS classes, backed by new responsive CSS stylesheets.

Changes

Post-Variant Responsive Grid Spans

Layer / File(s) Summary
Block contracts, CSS stylesheets, and entrypoint wiring
resources/js/visual-editor/blocks/post-template/block.json, resources/js/visual-editor/blocks/post-variant/block.json, resources/js/visual-editor/blocks/post-template/post-template.css, resources/js/visual-editor/blocks/post-template/index.ts, resources/js/visual-editor/blocks/post-variant/post-variant.css, resources/js/visual-editor/blocks/post-variant/index.ts, packages/visual-editor-renderer-blade/resources/assets/frontend/post-template.css, packages/visual-editor-renderer-blade/resources/assets/frontend/post-variant.css
post-template/block.json adds providesContext for layout/columns; post-variant/block.json adds gridColumnSpan/gridRowSpan attributes, usesContext entries, and artisanpackResponsive support. CSS stylesheets defining is-layout-grid grid columns and responsive ap-post-span-{N}-{bp}-{columns|row} rules are added for both the editor and Blade frontend, with CSS imports wired into each block's entrypoint.
QueryInliner: _resolvedGridSpan stamping
src/Resources/QueryInliner.php, tests/Unit/VisualEditor/Resources/QueryInlinerVariantSpansTest.php
expandQuery() detects grid layout, calls new resolveVariantSpans()/clampSpanValue() helpers, and stamps _resolvedGridSpan on each core/post-template-item when a matching variant has non-trivial spans. Unit tests cover grid/non-grid gating, default 1×1 omission, base+responsive merging, clamping, and variant precedence.
postTemplateItemSpanClasses helper (React and Vue)
packages/visual-editor-renderer-react/src/support/attributes.ts, packages/visual-editor-renderer-vue/src/support/attributes.ts, resources/js/visual-editor/blocks/__tests__/post-variant-grid-spans.test.ts
Adds postTemplateItemSpanClasses(value) to both React and Vue attribute modules with a breakpoint allowlist, clampSpan helper (1–12 range), and buildSpanClasses() that emits ap-post-span-* strings. Cross-renderer parity tests and block.json attribute contract tests cover all edge cases.
Editor UI: PostVariantEdit Grid Spans panel and PostTemplateEdit layout class
resources/js/visual-editor/blocks/post-variant/edit.tsx, resources/js/visual-editor/blocks/post-template/edit.tsx
PostVariantEdit gains PostVariantContext typing, isGridLayout helper, column count resolution, clamped span computation, conditional ap-post-span-* wrapper classes, and a new "Grid Spans" inspector panel with RangeControls. PostTemplateEdit switches to is-layout-grid/is-layout-flow classes to align with the Blade renderer's CSS-grid rules.
Renderer consumption: React, Vue, and Blade
packages/visual-editor-renderer-react/src/blocks/core/query.tsx, packages/visual-editor-renderer-vue/src/blocks/core/query.ts, packages/visual-editor-renderer-blade/resources/views/blocks/core/post-template-item.blade.php, packages/visual-editor-renderer-blade/src/View/Components/BlocksStylesComponent.php, packages/visual-editor-renderer-blade/resources/views/components/blocks-styles.blade.php, packages/visual-editor-renderer-blade/tests/Feature/PostVariantGridSpansRenderTest.php
React and Vue PostTemplateItemBlock renderers import and apply postTemplateItemSpanClasses to the item's classList. The Blade template reads _resolvedGridSpan, validates breakpoints via BreakpointRegistry, and emits ap-post-span-* classes. BlocksStylesComponent exposes two new stylesheet URL properties, and blocks-styles.blade.php emits the corresponding <link> tags. Feature tests verify end-to-end class emission.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(100, 149, 237, 0.5)
    note over Editor: Block authoring
    participant PostTemplateBlock
    participant PostVariantEdit
    PostTemplateBlock->>PostVariantEdit: provides artisanpack/postTemplateLayout, artisanpack/postTemplateColumns via context
    PostVariantEdit->>PostVariantEdit: isGridLayout() → show Grid Spans panel
    PostVariantEdit->>PostVariantEdit: RangeControl → sets gridColumnSpan, gridRowSpan attributes
  end
  rect rgba(144, 238, 144, 0.5)
    note over Server: Render-time inlining
    participant QueryInliner
    participant resolveVariantSpans
    participant PostTemplateItem as core/post-template-item
    QueryInliner->>resolveVariantSpans: variant block attributes (gridColumnSpan, gridRowSpan, breakpoint overrides)
    resolveVariantSpans-->>QueryInliner: _resolvedGridSpan record or null
    QueryInliner->>PostTemplateItem: stamp _resolvedGridSpan in attributes
  end
  rect rgba(255, 165, 0, 0.5)
    note over Renderers: Frontend class emission
    participant PostTemplateItem
    participant postTemplateItemSpanClasses as postTemplateItemSpanClasses (React/Vue)
    participant BladeTemplate as post-template-item.blade.php
    PostTemplateItem->>postTemplateItemSpanClasses: _resolvedGridSpan
    postTemplateItemSpanClasses-->>PostTemplateItem: ap-post-span-* class array
    PostTemplateItem->>BladeTemplate: _resolvedGridSpan
    BladeTemplate->>BladeTemplate: validate + clamp via BreakpointRegistry → ap-post-span-* classes
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • ArtisanPack-UI/visual-editor#505: Modifies the same core/query inliner and core/post-template-item rendering path that this PR extends with _resolvedGridSpan stamping and ap-post-span-* class emission.
  • ArtisanPack-UI/visual-editor#577: Extends BlocksStylesComponent for emitting additional frontend stylesheet URLs, similar to the addition of postTemplateStyleHref/postVariantStyleHref in this PR.
  • ArtisanPack-UI/visual-editor#598: Extends the same artisanpack/post-variant + QueryInliner integration; this PR builds on that foundation to compute and stamp _resolvedGridSpan grid span classes.

Poem

🐇 Hop, hop — the grid is set!
Each post-variant gets a net
of ap-post-span classes bright,
columns and row aligned just right.
Clamped to twelve, no overflow here —
responsive grids bring rabbit cheer! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.62% 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 title accurately summarizes the main feature: adding variable column and row span support for posts in Query Loop grid layouts.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 enhancement/592-variable-grid-spans-on-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 18, 2026
@ViewFromTheBox
ViewFromTheBox marked this pull request as ready for review June 18, 2026 00:30
@ViewFromTheBox ViewFromTheBox linked an issue Jun 18, 2026 that may be closed by this pull request
1 of 12 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

🤖 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/post-variant/edit.tsx`:
- Around line 57-71: The clampSpan function currently respects the max parameter
passed to it, but the backend renderers and CSS rules enforce a hardcoded
maximum of 12 columns. Modify the clampSpan function to ensure it never returns
a value greater than 12 to maintain parity with the renderer behavior, by
capping the maximum constraint to 12 in addition to the existing max parameter
check. Additionally, apply the same fix to the other span clamping logic at
lines 158-160 and 286-303 to ensure consistent column span handling across all
editor paths.
- Around line 95-101: The isGridLayout function currently falls back to checking
displayLayout.type === 'grid' even when artisanpack/postTemplateLayout is
explicitly set to a non-grid value. To fix this, ensure the post-template layout
is authoritative when present by checking if artisanpack/postTemplateLayout
exists in the context. If it exists, return the result of comparing it directly
to 'grid' without evaluating the displayLayout fallback. Only apply the
displayLayout.type === 'grid' check when artisanpack/postTemplateLayout is
undefined or absent from the context.

In `@src/Resources/QueryInliner.php`:
- Around line 628-630: The grid layout detection in the variable
`$postTemplateIsGrid` only checks if the layout attribute is a plain string
equal to 'grid', but it does not handle cases where the layout is serialized as
an object/array with a type property or as a separate layoutType attribute.
Expand the condition to also check if layout is an array with a 'type' key equal
to 'grid', or if a layoutType attribute exists and equals 'grid', so that all
valid grid layout formats are properly detected before stamping the
_resolvedGridSpan value.
🪄 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: 37734f18-3f61-47bd-890b-ee5bf257c90a

📥 Commits

Reviewing files that changed from the base of the PR and between 2cf2d0f and c6f6014.

📒 Files selected for processing (21)
  • packages/visual-editor-renderer-blade/resources/assets/frontend/post-template.css
  • packages/visual-editor-renderer-blade/resources/assets/frontend/post-variant.css
  • packages/visual-editor-renderer-blade/resources/views/blocks/core/post-template-item.blade.php
  • packages/visual-editor-renderer-blade/resources/views/components/blocks-styles.blade.php
  • packages/visual-editor-renderer-blade/src/View/Components/BlocksStylesComponent.php
  • packages/visual-editor-renderer-blade/tests/Feature/PostVariantGridSpansRenderTest.php
  • packages/visual-editor-renderer-react/src/blocks/core/query.tsx
  • packages/visual-editor-renderer-react/src/support/attributes.ts
  • packages/visual-editor-renderer-vue/src/blocks/core/query.ts
  • packages/visual-editor-renderer-vue/src/support/attributes.ts
  • resources/js/visual-editor/blocks/__tests__/post-variant-grid-spans.test.ts
  • resources/js/visual-editor/blocks/post-template/block.json
  • resources/js/visual-editor/blocks/post-template/edit.tsx
  • resources/js/visual-editor/blocks/post-template/index.ts
  • resources/js/visual-editor/blocks/post-template/post-template.css
  • 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/post-variant.css
  • src/Resources/QueryInliner.php
  • tests/Unit/VisualEditor/Resources/QueryInlinerVariantSpansTest.php

Comment thread resources/js/visual-editor/blocks/post-variant/edit.tsx
Comment thread resources/js/visual-editor/blocks/post-variant/edit.tsx
Comment thread src/Resources/QueryInliner.php Outdated
- Cap `resolveNumColumns()` / `clampSpan()` at the renderer's hardcoded
  ceiling of 12 so the editor slider never offers a value the renderer
  would silently clamp away (introduced `HARD_MAX_SPAN`).
- Make the post-template the authoritative source for grid detection
  in `isGridLayout()` — even when its `layout` is set to a non-grid
  value, fall through to the query's `displayLayout.type` only when
  the post-template hasn't published a layout key at all.
- Tolerate the object-form `layout = ['type' => 'grid']` and the
  sibling `layoutType` attribute in `QueryInliner::postTemplateLayoutIsGrid()`
  so hosts that wire post-template through Gutenberg's block-supports
  layout system still get spans stamped. New Pest case asserts both
  shapes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@ViewFromTheBox
ViewFromTheBox merged commit 95b69f8 into release/1.2 Jun 18, 2026
6 checks passed
@github-project-automation github-project-automation Bot moved this from In Review to Done in ArtisanPack UI Overview Jun 18, 2026
@ViewFromTheBox
ViewFromTheBox deleted the enhancement/592-variable-grid-spans-on-post-variants branch June 18, 2026 00:41
@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: Variable column/row spans for posts in Query Loop grid layout

1 participant