Skip to content

fix: emit per-block layout compound classes in the React and Vue renderers - #703

Merged
ViewFromTheBox merged 1 commit into
release/1.6from
bugfix/702-react-vue-renderers-per-block-layout-compound
Aug 6, 2026
Merged

ViewFromTheBox merged 1 commit into
release/1.6from
bugfix/702-react-vue-renderers-per-block-layout-compound

Conversation

@ViewFromTheBox

@ViewFromTheBox ViewFromTheBox commented Aug 6, 2026 •

Copy link
Copy Markdown
Member

Description

#700 fixed the missing wp-block-{slug}-is-layout-{type} compound class in the Blade renderer only, so the React and Vue renderers kept emitting the pre-fix markup and began rendering differently from Blade for the same block tree. This ports #700's approach to both JS renderers.

Closes: #702

Type of Change

  • Bug fix (fixes an issue)
  • New feature (adds new functionality)
  • Enhancement (improves existing functionality)
  • Refactoring (code improvement, no behavior change)
  • Documentation update
  • Performance improvement
  • Security fix
  • Breaking change (breaks backward compatibility)

Related Issue

Issue: #702 (follow-up to #700 / PR #701)

Motivation and Context

The three renderers are meant to be interchangeable — parity.test.ts describes them as sharing "the Blade partial contract" — but that test only compares React against Vue, never against Blade. That is why #700 was safe to land Blade-only, and also why the resulting divergence went unnoticed: a page rendered through React or Vue was no longer visually identical to the same tree rendered through Blade.

The block-library stylesheet targets the per-block compound exclusively for several rules (constrained containment on .wp-block-group, alignment handling on .wp-block-post-template), so emitting only the shared is-layout-* modifier left those rules unmatched.

Changes Made

  • Added layoutClass() / layoutPair() / layoutWrapperForBlock() to src/support/attributes.ts in both packages — the JS twin of LayoutSupport.php. layoutClass() resolves layout.type against a constrained|flex|flow|grid allowlist so an unknown stored value cannot mint an arbitrary class token. The slug is passed in explicitly rather than derived from the block name, because row and stack both render wp-block-group markup and need the group compound.
  • group, row, stack, buttons and post-template now pair each shared modifier with its per-block compound.
  • columns emits is-layout-flex + compound, where it previously emitted no layout class at all.
  • post-content now reads its layout attribute, defaulting to flow (matching group and upstream).
  • group gained the grid branch its ternary chain silently rendered as is-layout-flow — a pre-existing divergence, not fallout from Renderer emits shared layout class but not per-block layout class the block-library CSS targets #700.
  • is-layout-masonry deliberately stays unpaired: it is an ArtisanPack extension with no upstream compound.
  • LAYOUT_BASELINE_CSS gained the constrained-group containment rules mirroring rule set C in ThemeJsonTokensCompiler::compileLayoutRules().

Both packages change together because parity.test.ts asserts React and Vue produce byte-identical HTML — patching one alone fails the suite.

Note on the baseline CSS gating

The Blade side gates those containment rules on theme.json declaring contentSize / wideSize, but LAYOUT_BASELINE_CSS is a static string with no theme-token compiler behind it. The gating instead falls out of var() semantics: an undefined custom property makes the declaration invalid at computed-value time, so max-width resolves to its initial none.

The auto margins are not gated the same way — with no contentSize configured they still apply, which centers any child that sets its own width. That matches what WordPress core emits for a constrained layout, and it is documented inline as the one place this baseline goes further than the Blade renderer's gated output.

How Has This Been Tested?

Testing Environment:

  • Operating System: macOS (Darwin 27.0.0)
  • Browser (if applicable): n/a — renderer output asserted at the markup level
  • PHP Version: 8.4.3
  • Project Version: release/1.6

Tests Performed:

  1. npm test — 331 files / 3077 tests passing (was 3042; +35 new).
  2. ./vendor/bin/pest — 1700 passing, 1 skipped, 4493 assertions. The Blade renderer is untouched by this change and stays green.
  3. npx tsc --noEmit — 0 errors in either renderer package. (Pre-existing errors elsewhere under resources/js/ are unrelated and untouched.)
  4. npm run verify:parity — all 152 blocks registered across react / vue / blade.
  5. Manual verification in the dev app: rendered a constrained group through the React and Vue renderers and confirmed the wrapper class and the child containment now match the Blade output for the same tree.

Accessibility Tests Run

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

Details:

Not applicable — this change adds CSS class tokens to existing wrapper elements and adds layout rules to a stylesheet. No interactive elements, focus order, semantics, ARIA, or color values are introduced or altered. The one perceivable effect is that constrained groups now cap their unaligned children at the theme's content size, which is the intended (and upstream-standard) reading-width behavior.

Tests Added

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

Test details:

  • New tests/layoutCompound.test.tsx (React) and tests/layoutCompound.test.ts (Vue) — 13 tests each. Cover every layout-supporting wrapper's exact class string, the grid branch, post-content with and without a stored layout, post-template grid/flow/masonry, the LAYOUT_BASELINE_CSS containment rules, and the helper's allowlist fallback (including that a hostile layout.type cannot mint a class token).
  • New parity fixture in parity.test.ts covering the constrained and grid group branches plus a layout-carrying post-content, so React/Vue drift on the new paths is caught.
  • Updated the existing class-string assertions in BlockTree.test.* and PostAndSiteContextRendering.test.* to the new expected markup.

Documentation

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

Documentation details:

Docblocks on the three new helpers explaining the allowlist and why the slug is passed in explicitly rather than derived from the block name. Inline notes at each call site. The layoutBaselineCss.ts header previously said to keep the string byte-identical with the Blade partial; it now records the deliberate exception and why it exists.

Screenshots

n/a — the change is at the class-attribute level; the expected markup is asserted exactly in the new test suites.

Pre-Submission Checklist

  • Followed contributing guidelines
  • Checked for other open PRs for same update
  • Code passes all tests
  • Code has been linted
  • Accessibility tests completed
  • Code follows project style guide
  • Self-review completed
  • Comments added for complex code
  • No new warnings generated

Follow-up worth considering

The issue notes that nothing currently guards against the three renderers drifting on markup: scripts/verify-renderer-parity.mjs only checks that each renderer registers the same block names, and parity.test.ts only compares React against Vue. A Blade-vs-JS markup fixture check would have caught this class of bug at the point #700 landed. Not done here to keep the fix scoped — worth its own issue.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Enhancements

    • Improved layout class generation for groups, rows, stacks, columns, buttons, post content, and post templates.
    • Added consistent layout-specific classes across React and Vue renderers.
    • Improved handling of constrained layouts, including content-width, wide-width, and full-width children.
    • Added safe fallback behavior for unsupported layout types.
  • Tests

    • Expanded coverage for layout rendering, wrapper classes, masonry layouts, constrained groups, and React/Vue parity.

…erers

#700 fixed the missing `wp-block-{slug}-is-layout-{type}` compound in the
Blade renderer only, leaving React and Vue emitting the pre-fix markup and
rendering differently from Blade for the same block tree.

Ports the Blade approach to both JS renderers via a shared helper alongside
`classList()` — `layoutClass()` resolves `layout.type` against an allowlist
so an unknown stored value can't mint an arbitrary class token, `layoutPair()`
appends the per-block compound, and `layoutWrapperForBlock()` combines them.
The slug is passed in explicitly because `row` and `stack` both render
`wp-block-group` markup and need the `group` compound, not their own.

Closes the four gaps the issue enumerates:

- group / row / stack / buttons / post-template now pair each shared
  `is-layout-*` modifier with its compound
- columns emits `is-layout-flex` + compound, where it previously emitted
  no layout class at all
- post-content reads its `layout` attribute, defaulting to `flow`
- group gained the `grid` branch its ternary chain silently rendered as flow

`is-layout-masonry` stays unpaired — it is an ArtisanPack extension with no
upstream compound.

`LAYOUT_BASELINE_CSS` gains the constrained-group containment rules mirroring
rule set C in `ThemeJsonTokensCompiler::compileLayoutRules()`. Blade gates
those on theme.json declaring `contentSize` / `wideSize`; this string is
static, so the gating falls out of `var()` semantics instead — an undefined
custom property makes the declaration invalid at computed-value time and
`max-width` resolves to its initial `none`.

Both packages change together because `parity.test.ts` asserts React and Vue
produce byte-identical HTML. Adds a parity fixture covering the constrained /
grid group branches and a layout-carrying post-content, plus a
`layoutCompound` suite per package covering every wrapper and the helper's
allowlist fallback.

Closes #702

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 6, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

React and Vue now share layout class serialization helpers. Layout-supporting blocks emit generic and block-specific classes. Post content and post templates use the helpers. Baseline CSS constrains group children. Tests cover fallback behavior, rendering, CSS, and parity.

Changes

Layout class parity

Layer / File(s) Summary
Layout serialization helpers
packages/visual-editor-renderer-react/src/support/attributes.ts, packages/visual-editor-renderer-vue/src/support/attributes.ts
Added supported layout resolution, fallback handling, compound class pairing, and block wrapper helpers.
Renderer layout integration
packages/visual-editor-renderer-react/src/blocks/core/layout.tsx, packages/visual-editor-renderer-vue/src/blocks/core/layout.ts, packages/visual-editor-renderer-react/src/blocks/core/postContext.tsx, packages/visual-editor-renderer-vue/src/blocks/core/postContext.ts, packages/visual-editor-renderer-react/src/blocks/core/query.tsx, packages/visual-editor-renderer-vue/src/blocks/core/query.ts
Updated group, row, stack, columns, buttons, post-content, and post-template rendering to emit paired layout classes. Masonry remains unpaired.
Baseline CSS and validation
packages/visual-editor-renderer-react/src/support/layoutBaselineCss.ts, packages/visual-editor-renderer-vue/src/support/layoutBaselineCss.ts, packages/visual-editor-renderer-react/tests/*, packages/visual-editor-renderer-vue/tests/*
Added constrained-group containment rules and tests for helpers, rendered classes, fallback behavior, masonry handling, CSS selectors, and React/Vue parity.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant BlockTree
  participant LayoutBlock
  participant Attributes
  participant Markup
  BlockTree->>LayoutBlock: Render layout-supporting block
  LayoutBlock->>Attributes: Resolve layoutClass and layoutPair
  Attributes-->>LayoutBlock: Return shared and compound classes
  LayoutBlock->>Markup: Apply classes to wrapper
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% 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
Linked Issues check ✅ Passed All objectives from #702 are addressed: per-block compounds added, layout classes for columns added, post-content layout attribute handling added, group grid branch corrected, masonry kept unpaired, constrained-group rules added, wrapper slug used for compounds, and parity maintained across renderers.
Out of Scope Changes check ✅ Passed All changes are within scope: shared layout helpers, layout-supporting block updates, baseline CSS additions, test updates, and parity fixtures directly address the requirements from #702.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding per-block layout compound classes to both React and Vue renderers.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bugfix/702-react-vue-renderers-per-block-layout-compound

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@ViewFromTheBox ViewFromTheBox moved this from Backlog to In Review in ArtisanPack UI Overview Aug 6, 2026
@ViewFromTheBox
ViewFromTheBox marked this pull request as ready for review August 6, 2026 14:42
@ViewFromTheBox
ViewFromTheBox merged commit ea761ef into release/1.6 Aug 6, 2026
6 checks passed
@ViewFromTheBox
ViewFromTheBox deleted the bugfix/702-react-vue-renderers-per-block-layout-compound branch August 6, 2026 14:42
@github-project-automation github-project-automation Bot moved this from In Review to Done in ArtisanPack UI Overview Aug 6, 2026
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.

1 participant