Skip to content

feat: native flexbox layout controls for group/column/columns/grid-item - #596

Merged
ViewFromTheBox merged 5 commits into
release/1.2from
feature/595-native-flex-layout-controls
Jun 16, 2026
Merged

ViewFromTheBox merged 5 commits into
release/1.2from
feature/595-native-flex-layout-controls

Conversation

@ViewFromTheBox

@ViewFromTheBox ViewFromTheBox commented Jun 16, 2026 •

Copy link
Copy Markdown
Member

Description

Native Flex Layout + Flex Item inspector panels on artisanpack/group, artisanpack/column, artisanpack/columns, and artisanpack/grid-item. Every flexbox property is exposed per-breakpoint via the existing <ViewportSwitcher />, replacing both core's narrow Flex layout variation (on group) and crosswinds-blocks' cbInnerLayout enum.

Closes: #595

Type of Change

  • New feature (adds new functionality)

Related Issue

Issue: #595

Motivation and Context

WordPress core's Flex layout variation exposes only direction, justify, vertical alignment, wrap, and blockGap — and only when layout.type === 'flex' is set. Crosswinds-blocks' legacy plugin solved a real authoring problem (fill-height, push-to-bottom, center-children) but masked the full flexbox surface behind a five-option enum. Since we own all four affected blocks, we can ship a first-class Flex panel with the full property surface and per-breakpoint responsiveness.

Changes Made

  • Shared serializer + fixtures at resources/js/visual-editor/blocks/_shared/flex-controls/. Single source of truth for class generation; fixtures.json drives parity tests across all 4 packages.
  • Inspector UI: FlexContainerControls (enable, direction, wrap, justify, align-items, align-content, place-content, gap) + FlexItemControls (align-self, grow, shrink, basis, order) + useFlexParent walking the block tree once for the disabled state.
  • block.json: artisanpackFlex attribute added to group / column / columns / grid-item, plus entry in each block's supports.artisanpackResponsive.attributes.
  • edit.tsx + save.tsx wired for all 4 blocks: serializer output merges into wrapper className.
  • Group deprecation appended to the chain (entry 6): migrates layout.type === 'flex' → artisanpackFlex.container on first edit. Unedited content unchanged.
  • Core Flex variation suppressed on artisanpack/group via a blocks.registerBlockType filter.
  • Utility stylesheet at resources/css/flex-layout.css (ap-flex-* classes with sm:/md:/lg:/xl:/2xl: prefixes). Arbitrary values like ap-gap-x-[16px] emit per-page via the renderer.
  • Blade renderer (packages/visual-editor-renderer-blade): FlexSupport.php mirrors the TS serializer; arbitrary-value rules push into the existing ResponsiveCssAccumulator. Templates for group / column / columns / grid-item all merge the new classes.
  • React + Vue renderers: support/flex-serializer.ts ports the JS serializer with the same fixtures. Block components emit the same classes.
  • theme.json defaults under settings.artisanpack.flex (enable / defaultDirection / defaultJustifyContent / defaultAlignItems / defaultGap).
  • Docs: docs/blocks/Flex-Layout.md, docs/migration/From-Crosswinds-Blocks.md, CHANGELOG entry under [Unreleased].

How Has This Been Tested?

Testing Environment:

  • Operating System: macOS (Darwin 25.5.0)
  • PHP Version: 8.4.3
  • Project Version: 1.2.x

Tests Performed:

  1. Cross-renderer parity — every fixture in fixtures.json runs through 4 test suites (editor, Blade, React, Vue). 58 parity assertions total; all green.
  2. Block edit/save tests — group/column/columns/grid-item edit + save tests all pass (80/80).
  3. Group deprecation chain — entry count + first-entry isEligible/migrate behavior asserted unchanged; new flex-migration entry appended at index 5.
  4. Dev-app integration — 4 Pest tests in artisanpack-ui-dev/tests/Feature/VisualEditor/FlexRendererTest.php exercise the full renderer registration with artisanpackFlex attributes; all assertions pass.

Accessibility Tests Run

  • Keyboard navigation tested — <ToggleGroupControl> primitives inherit core's keyboard/focus management.
  • ARIA labels checked — Inspector controls use translated label props on every WP component.

Details: Icon-button groups are stock @wordpress/components ToggleGroupControl, identical to core's justifyContent UI. The Advanced section uses native <details>/<summary>. Reverse direction variants surface a written hint about visual vs DOM order.

Tests Added

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

Test details:

  • resources/js/visual-editor/blocks/_shared/flex-controls/__tests__/serializer.test.ts — 15 fixtures + null-input case (editor TS serializer).
  • packages/visual-editor-renderer-blade/tests/Feature/FlexSupportTest.php — 14 fixtures + null-input case (Blade PHP mirror).
  • packages/visual-editor-renderer-react/tests/flex-serializer.test.ts — 14 fixtures (React TS mirror).
  • packages/visual-editor-renderer-vue/tests/flex-serializer.test.ts — 14 fixtures (Vue TS mirror).
  • Updated group/column edit-test mocks for new @wordpress/components and @wordpress/element imports.
  • Updated group deprecated-test length expectation (5 → 6) for the new flex migration entry.

Documentation

  • Inline code documentation added
  • Wiki updated (docs files)

Documentation details:

  • docs/blocks/Flex-Layout.md — full inspector reference: panel locations, every container/item control with CSS property + values, generated class shape, theme.json defaults, renderer parity story.
  • docs/migration/From-Crosswinds-Blocks.md — hand-migration table mapping cbUseFlex / cbFillHeight / cbInnerLayout to the new attribute shape.
  • CHANGELOG.md — entry under [Unreleased].

Screenshots

Not included in this draft — visual verification requires the editor canvas + the dev-app's /components/flex-layout route (also added in this PR via the artisanpack-ui-dev repo).

Pre-Submission Checklist

  • Code passes all tests (editor + Blade + React + Vue + dev-app integration all green)
  • Code follows project style guide (Pint applied to dev-app test; package code-style follows existing conventions)
  • Self-review completed
  • Comments added for complex code (CORE_JUSTIFY_MAP migration table, arbitrary-value emitter rationale, parent-detector implicit-flex note)
  • No new warnings generated (TS errors that exist in the codebase are pre-existing @wordpress/* declaration noise)

Notes for Reviewer

  • The serializer's emit order is deterministic: container properties first, then item properties; per attribute, the responsive cascade walks [base, sm, md, lg, xl, 2xl]. This order is asserted by the fixture set and must hold across all 4 implementations.
  • Group block deprecation is appended at index 5 (after the 5 upstream-ported entries) rather than prepended; this preserves the index-0 contract that existing tests depend on.
  • The artisanpack-ui-dev repo has matching changes (new /components/flex-layout route + Pest test) — those land separately.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features
    • Added a native Flex Layout inspector panel for Group, Columns, Column, and Grid Item blocks with full responsive container/item controls
    • Added flex utility styling with Tailwind-like responsive classes, including arbitrary values for place-content and gaps
    • Auto-migration converts legacy flex layout settings on first edit
  • Documentation
    • Added Flex Layout documentation plus a legacy-to-UI migration guide
  • Bug Fixes
    • Prevented spacing/margin artifacts inside flex wrappers
  • Tests
    • Added cross-renderer parity checks to keep output consistent across Blade, React, and Vue

ViewFromTheBox and others added 2 commits June 16, 2026 09:43
…em (#595)

Replace the narrow `cbInnerLayout` enum + WordPress core's minimal
Flex variation with a full Flex Layout + Flex Item inspector on
`artisanpack/group`, `artisanpack/column`, `artisanpack/columns`, and
`artisanpack/grid-item`. Every property is per-breakpoint via the
existing ViewportSwitcher.

- Shared `_shared/flex-controls/` (serializer + fixtures + types +
  container/item React panels + parent detector + responsive helpers).
- Tailwind-shaped utility class scheme (`ap-flex`, `md:ap-justify-end`,
  `ap-gap-x-[1rem]`, …) plus a static stylesheet and per-page arbitrary
  rule emission.
- Blade `FlexSupport`, mirrored TS serializers in the React + Vue
  renderers, all asserted byte-identical against `fixtures.json` by
  Pest + vitest suites (15+15+14+14 = 58 parity assertions).
- Group block: filter suppresses core's Flex layout variation; new
  deprecation migrates `layout.type === 'flex'` to
  `artisanpackFlex.container` on first edit.
- theme.json defaults under `settings.artisanpack.flex`.
- Docs: `docs/blocks/Flex-Layout.md` + crosswinds migration note +
  CHANGELOG entry.

Closes #595

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Fix Vue serializer header (copy-paste from React renderer left
  "React renderer" in the JSDoc).
- Validate file_get_contents + json_decode in the Blade FlexSupport
  Pest fixture loader so a missing or malformed fixtures.json fails
  with a clear message instead of a cryptic null access.

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: feb5e6e5-d7aa-4ffb-aa03-81cf4fc687b4

📥 Commits

Reviewing files that changed from the base of the PR and between b0ef484 and 1554797.

📒 Files selected for processing (3)
  • packages/visual-editor-renderer-blade/src/Support/FlexSupport.php
  • packages/visual-editor-renderer-react/src/support/flex-serializer.ts
  • packages/visual-editor-renderer-vue/src/support/flex-serializer.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/visual-editor-renderer-vue/src/support/flex-serializer.ts
  • packages/visual-editor-renderer-react/src/support/flex-serializer.ts
  • packages/visual-editor-renderer-blade/src/Support/FlexSupport.php

📝 Walkthrough

Walkthrough

Adds a native flex layout inspector panel to artisanpack/group, column, columns, and grid-item blocks. Introduces a shared TypeScript serializer emitting responsive Tailwind-like utility classes (ap-flex-*) plus arbitrary-value rules, mirrored in React, Vue, and Blade renderer packages with byte-identical parity verified by shared fixture tests. Includes a deprecation migration for legacy layout.type === 'flex' group blocks, core-flex suppression on the group block, canvas style injection for the editor preview, and a theme.json defaults extension.

Changes

Native Flex Layout Panel

Layer / File(s) Summary
Flex attribute types and shared editor serializer
resources/js/visual-editor/blocks/_shared/flex-controls/types.ts, resources/js/visual-editor/blocks/_shared/flex-controls/serializer.ts, resources/js/visual-editor/blocks/_shared/flex-controls/__tests__/serializer.test.ts, resources/js/visual-editor/blocks/_shared/flex-controls/fixtures.json, resources/js/visual-editor/blocks/_shared/flex-controls/index.ts
Defines flex CSS value unions (FlexDirectionValue, FlexWrapValue, etc.) and ArtisanpackFlexAttribute container/item structure. Implements serializeFlexContainer, serializeFlexItem, and serializeFlex with token maps, breakpoint cascade resolution, canonical numeric sets, and arbitrary-rule emission. Adds shared cross-renderer parity fixture data and barrel re-exports all public symbols.
Responsive helpers, parent detector, and inspector controls
resources/js/visual-editor/blocks/_shared/flex-controls/responsive-helpers.ts, resources/js/visual-editor/blocks/_shared/flex-controls/parent-detector.ts, resources/js/visual-editor/blocks/_shared/flex-controls/container-controls.tsx, resources/js/visual-editor/blocks/_shared/flex-controls/item-controls.tsx
Adds readAt/writeAt helpers for per-breakpoint slot management. Implements useFlexParent hook to detect flex-container ancestry. Adds FlexContainerControls panel (enable/direction/wrap/justify/align/gap with Advanced section for place-content) and FlexItemControls panel (align-self/grow/shrink/basis/order, disabled when parent is not a flex container).
Flex utility stylesheet and canvas injection
resources/css/flex-layout.css, packages/visual-editor-renderer-blade/resources/assets/frontend/flex-layout.css, resources/js/visual-editor/editor/canvas-styles.ts, resources/js/visual-editor/blocks/column/index.ts, resources/js/visual-editor/blocks/columns/index.ts, resources/js/visual-editor/blocks/grid-item/index.ts
Declares complete ap-flex-* utility class set for base and five responsive breakpoints (sm/md/lg/xl/2xl) covering all flexbox properties. Injects flex utilities into editor canvas iframe via canvas-styles. Block entrypoints import the stylesheet for build-time inclusion.
Block schema and editor/save wiring for all four blocks
resources/js/visual-editor/blocks/group/, resources/js/visual-editor/blocks/column/, resources/js/visual-editor/blocks/columns/, resources/js/visual-editor/blocks/grid-item/ (block.json, edit.tsx, save.tsx), resources/theme-json/default-base.php
Extends all four block schemas with artisanpackFlex attribute. Updates edit and save components to serialize artisanpackFlex into class lists and render the new inspector controls. Extends editor test mocks to support control dependencies. Adds artisanpack.flex theme.json defaults with enable, defaultDirection, and gap settings.
Group block: core-flex suppression and deprecation migration
resources/js/visual-editor/blocks/group/suppress-core-flex.ts, resources/js/visual-editor/blocks/group/deprecated.tsx, resources/js/visual-editor/blocks/group/index.ts, resources/js/visual-editor/blocks/group/__tests__/deprecated.test.tsx
Registers a filter to strip core flex layout default from artisanpack/group. Appends deprecation entry that migrates layout.type === 'flex' blocks to artisanpackFlex.container on first edit with alignment mapping and legacy key cleanup. Updates deprecation count test.
React renderer serializer, block wiring, and parity test
packages/visual-editor-renderer-react/src/support/flex-serializer.ts, packages/visual-editor-renderer-react/src/blocks/core/layout.tsx, packages/visual-editor-renderer-react/src/blocks/artisanpack/grid.tsx, packages/visual-editor-renderer-react/src/BlockTree.tsx, packages/visual-editor-renderer-react/tests/flex-serializer.test.ts
Adds standalone flex serializer with identical logic and token maps. Wires flexClassNames into group/column/columns/grid-item renderers. Updates BlockTree to collect and inject aggregated arbitrary-rule CSS. Adds fixture-driven Vitest parity test verifying byte-identical output.
Vue renderer serializer, block wiring, and parity test
packages/visual-editor-renderer-vue/src/support/flex-serializer.ts, packages/visual-editor-renderer-vue/src/blocks/core/layout.ts, packages/visual-editor-renderer-vue/src/blocks/artisanpack/grid.ts, packages/visual-editor-renderer-vue/src/BlockTree.ts, packages/visual-editor-renderer-vue/tests/flex-serializer.test.ts
Adds standalone flex serializer with identical token maps and logic. Wires flexClassNames into group/column/columns/grid-item renderers. Updates BlockTree to collect and inject aggregated arbitrary CSS. Adds fixture-driven Vitest parity test.
Blade FlexSupport, service provider, templates, and parity test
packages/visual-editor-renderer-blade/src/Support/FlexSupport.php, packages/visual-editor-renderer-blade/src/VisualEditorRendererBladeServiceProvider.php, packages/visual-editor-renderer-blade/resources/views/blocks/core/group.blade.php, packages/visual-editor-renderer-blade/resources/views/blocks/core/columns.blade.php, packages/visual-editor-renderer-blade/resources/views/blocks/core/column.blade.php, packages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/grid-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/FlexSupportTest.php
Adds PHP FlexSupport class with container/item serialization, arbitrary CSS building, and helpers. Registers as scoped binding. Updates four Blade templates to merge flex wrapper classes. Updates BlocksStylesComponent to include frontend flex stylesheet. Adds fixture-driven and null-input Pest parity tests.
Documentation and changelog
docs/blocks/Flex-Layout.md, docs/migration/From-Crosswinds-Blocks.md, CHANGELOG.md
Adds Flex-Layout.md covering panel behavior, class output format, theme.json defaults, renderer parity, and migration. Adds From-Crosswinds-Blocks.md with manual attribute-mapping table. Updates CHANGELOG with feature summary.

Sequence Diagram(s)

sequenceDiagram
  participant Editor as WP Block Editor
  participant FlexControls as FlexContainerControls
  participant Serializer as serializeFlex
  participant BlockEdit as BlockEdit (group/column/etc)
  participant SaveFn as Save Component
  participant Renderer as React/Vue/Blade

  Editor->>FlexControls: user changes flex property at breakpoint
  FlexControls->>FlexControls: writeAt(attribute, breakpoint, value)
  FlexControls->>BlockEdit: onChange(updatedArtisanpackFlex)
  BlockEdit->>Serializer: serializeFlex(artisanpackFlex, registry)
  Serializer-->>BlockEdit: { classes, arbitraryRules }
  BlockEdit->>Editor: useBlockProps className includes flex classes
  Editor->>SaveFn: save block
  SaveFn->>Serializer: serializeFlex(artisanpackFlex, registry)
  Serializer-->>SaveFn: { classes }
  SaveFn-->>Editor: saved HTML with ap-flex-* classes
  Editor->>Renderer: render saved HTML
  Renderer->>Renderer: flexClassNames / FlexSupport::wrapperForBlock
  Renderer-->>Editor: wrapper element with ap-flex-* classes
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • ArtisanPack-UI/visual-editor#510: Introduced the ResponsiveCssAccumulator and BreakpointRegistry Blade services that this PR extends with the FlexSupport scoped binding and arbitrary CSS rule accumulation.
  • ArtisanPack-UI/visual-editor#576: Both PRs modify resources/js/visual-editor/editor/canvas-styles.ts to change what CSS gets injected into the editor BlockCanvas iframe; this PR adds flex-layout utilities.
  • ArtisanPack-UI/visual-editor#540: This PR's flex-layout.css includes :where(.ap-flex) > * + * spacing resets to neutralize the flow baseline margins introduced in that PR.

Poem

🐰 Hop hop, a flex panel appears,
With ap-flex-row and justify cheers!
Each breakpoint sings sm:ap-flex-col,
React, Vue, and Blade—parity the goal.
Legacy flex migrates with a gentle nudge,
No inline styles—just classes that won't budge! 🎉

✨ 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/595-native-flex-layout-controls

@ViewFromTheBox ViewFromTheBox moved this from Backlog to In Review in ArtisanPack UI Overview Jun 16, 2026

@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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
resources/css/flex-layout.css (1)

105-247: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Responsive utility coverage is incomplete versus serializer output.

Lines 105-247 define only partial breakpoint variants, while the serializer can emit a wider set (e.g., *:ap-items-self-start, *:ap-content-evenly, *:ap-grow-9, *:ap-order-9, xl:ap-flex-wrap-reverse, 2xl:ap-flex-row-reverse). Missing selectors make those controls silently no-op at affected breakpoints. Please align responsive utility generation to the full serializer token matrix.

🤖 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/css/flex-layout.css` around lines 105 - 247, The responsive flex
utility classes are incomplete across multiple breakpoints. The serializer can
generate variants like flex-row-reverse, flex-col-reverse, content-evenly, grow
values up to 9, order values up to 9, and other combinations that are missing
from the CSS media queries. Add the following missing utilities to each
breakpoint section: in the sm (min-width: 640px) and md (min-width: 768px)
sections, the coverage appears more complete, but verify all flex property
variants match the serializer output; in the lg (min-width: 1024px) section, add
the missing flex-row-reverse, flex-col-reverse, flex-wrap-reverse,
justify-around, justify-evenly, and all align/order utilities that are present
at lower breakpoints; in the xl (min-width: 1280px) section, significantly
expand coverage to include flex-row-reverse, flex-col-reverse,
flex-wrap-reverse, justify-around, justify-evenly, all items variants, all
content variants, all self variants, grow-0 through grow-9, shrink-0 and
shrink-1, basis utilities, and order-0 through order-9; in the 2xl (min-width:
1536px) section, add flex-col-reverse, flex-wrap-reverse, justify-around,
justify-between, justify-evenly, items-start, items-end, items-baseline,
items-stretch, all content variants, all self variants, grow/shrink variants,
basis utilities, and order utilities to match the full serializer matrix. Ensure
all utility classes defined at lower breakpoints are available at higher
breakpoints to prevent silent no-ops when those selectors are generated.
🧹 Nitpick comments (4)
resources/js/visual-editor/blocks/_shared/flex-controls/fixtures.json (1)

3-231: ⚡ Quick win

Add a fixture for responsive flex deactivation behavior.

Please add a case for enabled: { "base": true, "md": false } (after reset semantics are implemented) so parity tests lock the expected class output and prevent regressions on breakpoint deactivation.

🤖 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/blocks/_shared/flex-controls/fixtures.json` around
lines 3 - 231, Add a new fixture object to the fixtures array that tests
responsive flex deactivation behavior. The fixture should have a descriptive
name like "responsive-deactivation" and set the input with container.enabled
having base set to true and md set to false. Define the expected output to show
what container classes are generated at the base breakpoint (which should
include the "ap-flex" class) and what reset or deactivation class is generated
at the md breakpoint to prevent regressions when flex is disabled at specific
breakpoints.
resources/css/flex-layout.css (1)

104-247: 🏗️ Heavy lift

Generate responsive blocks from shared token definitions to prevent drift.

The responsive sections are hand-maintained and have already diverged from base/serializer capabilities. Consider generating breakpoint variants from a single token source to keep CSS and serializers in lockstep.

🤖 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/css/flex-layout.css` around lines 104 - 247, The responsive utility
blocks (sm:, md:, lg:, xl:, 2xl: prefixed classes) within the media queries are
hand-maintained and have become inconsistent—notice how xl and 2xl blocks have
fewer properties than sm, md, and lg. Instead of manually coding each breakpoint
variant, refactor the CSS generation process to create all responsive blocks
programmatically from a single shared token source. This will ensure all
breakpoints remain in lockstep with the base utilities and prevent future drift
between the CSS output and any serializers that depend on these same token
definitions.
resources/js/visual-editor/blocks/group/__tests__/deprecated.test.tsx (1)

25-29: ⚡ Quick win

Strengthen the migration assertion beyond array length.

This only verifies count, so a broken/reordered #595 entry could still pass. Add a focused check that the appended entry is eligible for layout.type === 'flex' and that migration outputs artisanpackFlex.container.enabled.base === true while dropping layout.type.

Suggested test addition
 it('ships the upstream five entries plus the `#595` flex-layout migration', () => {
     expect(Array.isArray(deprecated)).toBe(true);
     // 5 upstream-ported entries + 1 `#595` flex-layout migration appended.
     expect(deprecated).toHaveLength(6);
+    const flexMigration = deprecated[5] as {
+        isEligible: (attrs: Record<string, unknown>) => boolean;
+        migrate: (attrs: Record<string, unknown>) => Record<string, unknown> | undefined;
+    };
+    expect(flexMigration.isEligible({ layout: { type: 'flex' } })).toBe(true);
+    const migrated = flexMigration.migrate({
+        layout: { type: 'flex', orientation: 'vertical', justifyContent: 'center' },
+    }) as Record<string, any>;
+    expect(migrated.artisanpackFlex.container.enabled.base).toBe(true);
+    expect(migrated.layout?.type).toBeUndefined();
 });
🤖 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/blocks/group/__tests__/deprecated.test.tsx` around
lines 25 - 29, The test 'ships the upstream five entries plus the `#595`
flex-layout migration' only verifies that the deprecated array has the correct
length, which does not validate the actual content or behavior of the appended
`#595` entry. Strengthen the test by adding assertions that check the appended
migration entry is eligible for layout.type === 'flex', verify the migration
outputs artisanpackFlex.container.enabled.base === true, and confirm the
migration drops the layout.type property. This ensures the migration logic is
functioning correctly, not just that the array has the expected count.
packages/visual-editor-renderer-blade/tests/Feature/FlexSupportTest.php (1)

16-54: ⚡ Quick win

Add a wrapper-level accumulator regression test.

Current coverage validates serialize() only. It won’t catch the Line 56 scope-collision path in FlexSupport::wrapperForBlock() when multiple blocks with different arbitrary values are rendered in the same request.

💡 Suggested test shape
+it( 'accumulates arbitrary flex css for multiple distinct blocks', function () {
+	$accumulator = app( \ArtisanPackUI\VisualEditorRendererBlade\Services\ResponsiveCssAccumulator::class );
+
+	FlexSupport::wrapperForBlock( [ 'artisanpackFlex' => [ 'container' => [ 'gap' => [ 'row' => '8px' ] ] ] ] );
+	FlexSupport::wrapperForBlock( [ 'artisanpackFlex' => [ 'container' => [ 'gap' => [ 'row' => '16px' ] ] ] ] );
+
+	$css = $accumulator->all();
+
+	expect( $css )->toContain( 'row-gap: 8px;' )
+		->and( $css )->toContain( 'row-gap: 16px;' );
+} );
🤖 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 `@packages/visual-editor-renderer-blade/tests/Feature/FlexSupportTest.php`
around lines 16 - 54, Add a new test to validate the wrapper-level accumulator
behavior in FlexSupport::wrapperForBlock() to catch scope-collision regressions.
The test should create multiple blocks with different arbitrary values and
render them in sequence within the same request context, then verify that each
block's output is correct and that the arbitrary values from different blocks do
not leak into or override each other. This ensures the accumulator logic
properly isolates state between different block instances.
🤖 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 `@docs/blocks/Flex-Layout.md`:
- Around line 76-97: The documentation at lines 72-74 (sibling) contains
incomplete and partially incorrect claims about arbitrary-rule CSS emission.
Update that section to clarify that only the Blade renderer implements
arbitrary-rule CSS emission via inline <style data-ve-responsive> tags using
FlexSupport::buildArbitraryStyles() and ResponsiveCssAccumulator to flush
grouped CSS with `@media` queries by breakpoint. Explicitly state that React and
Vue renderers collect arbitraryRules in serializeFlex() but lack the
buildArbitraryStyles() function and CSS emission mechanism — these renderers
only expose flexClassNames() with classes only, meaning arbitrary values like
ap-gap-x-[3.5rem] will render as class names but their corresponding CSS rules
will not be emitted, breaking styling for those values in React and Vue. The
Theme.json defaults structure documentation at lines 76-97 (anchor) requires no
changes as it is verified correct and matches the actual implementation in
resources/theme-json/default-base.php.

In `@packages/visual-editor-renderer-blade/src/Support/FlexSupport.php`:
- Around line 53-57: The hardcoded scope key `flex-arbitrary` in the
ResponsiveCssAccumulator::push() call causes all arbitrary flex CSS to be
deduplicated under the same scope, which drops later blocks with different
arbitrary flex values. Instead of using the hardcoded string `flex-arbitrary`,
generate a unique scope key that incorporates the actual CSS content (such as by
creating a hash or checksum of the CSS). This ensures each distinct arbitrary
flex CSS gets its own unique scope while still allowing deduplication of
identical rules.
- Around line 393-395: The is_numeric() check on line 393 incorrectly accepts
string values like "1.5" which then coerce to integer 1 via (int) casting,
potentially emitting wrong canonical classes. Replace the is_numeric($value)
check with a regex pattern test using /^-?\d+$/ to match only true integer
strings (allowing optional leading minus sign) and reject float strings,
aligning with the TypeScript serializer's shared contract. This ensures numeric
value validation only accepts plain integer tokens, not float representations.

In `@resources/js/visual-editor/blocks/_shared/flex-controls/item-controls.tsx`:
- Around line 91-93: The current implementation uses CSS-only disabling with
pointerEvents: 'none' on the fieldsetStyle, which only blocks pointer events but
leaves keyboard accessibility intact, allowing users to still tab to and
interact with controls via keyboard. Replace the CSS-based approach by applying
a proper disabled attribute or prop to the actual form control elements within
the fieldset (at the main location in the item-controls file around lines 91-93
and the sibling location around lines 104-155). Instead of relying on opacity
and pointerEvents styling, pass the disabled variable as a prop to the form
controls themselves, ensuring they are semantically disabled and inaccessible
via both pointer and keyboard input.

In `@resources/js/visual-editor/blocks/_shared/flex-controls/serializer.ts`:
- Around line 189-193: The emitForEachBreakpoint callback in the serializer only
handles the true case for enabled, emitting the ap-flex class, but does not emit
any reset class when enabled is false. This causes false overrides at larger
breakpoints to be ineffective due to CSS cascading. Add an else branch in the
callback that emits a reset or non-flex utility class when value is false, and
ensure corresponding responsive CSS utilities are defined to support this reset
class for each breakpoint.

In `@resources/js/visual-editor/blocks/column/__tests__/edit.test.tsx`:
- Around line 43-48: The useState mock in the vi.mock block does not respect
lazy initializer semantics. When a function is passed as the initial value to
useState (a lazy initializer pattern), the mock should execute that function and
use its return value, rather than returning the function itself. Modify the
useState mock definition to check if the initial parameter is a function; if it
is, call it and use the result as the state value, otherwise use the initial
value directly. This ensures that when useActiveBreakpointValue() or other code
uses lazy initializers, the state is correctly initialized with the computed
value rather than the function object.

---

Outside diff comments:
In `@resources/css/flex-layout.css`:
- Around line 105-247: The responsive flex utility classes are incomplete across
multiple breakpoints. The serializer can generate variants like
flex-row-reverse, flex-col-reverse, content-evenly, grow values up to 9, order
values up to 9, and other combinations that are missing from the CSS media
queries. Add the following missing utilities to each breakpoint section: in the
sm (min-width: 640px) and md (min-width: 768px) sections, the coverage appears
more complete, but verify all flex property variants match the serializer
output; in the lg (min-width: 1024px) section, add the missing flex-row-reverse,
flex-col-reverse, flex-wrap-reverse, justify-around, justify-evenly, and all
align/order utilities that are present at lower breakpoints; in the xl
(min-width: 1280px) section, significantly expand coverage to include
flex-row-reverse, flex-col-reverse, flex-wrap-reverse, justify-around,
justify-evenly, all items variants, all content variants, all self variants,
grow-0 through grow-9, shrink-0 and shrink-1, basis utilities, and order-0
through order-9; in the 2xl (min-width: 1536px) section, add flex-col-reverse,
flex-wrap-reverse, justify-around, justify-between, justify-evenly, items-start,
items-end, items-baseline, items-stretch, all content variants, all self
variants, grow/shrink variants, basis utilities, and order utilities to match
the full serializer matrix. Ensure all utility classes defined at lower
breakpoints are available at higher breakpoints to prevent silent no-ops when
those selectors are generated.

---

Nitpick comments:
In `@packages/visual-editor-renderer-blade/tests/Feature/FlexSupportTest.php`:
- Around line 16-54: Add a new test to validate the wrapper-level accumulator
behavior in FlexSupport::wrapperForBlock() to catch scope-collision regressions.
The test should create multiple blocks with different arbitrary values and
render them in sequence within the same request context, then verify that each
block's output is correct and that the arbitrary values from different blocks do
not leak into or override each other. This ensures the accumulator logic
properly isolates state between different block instances.

In `@resources/css/flex-layout.css`:
- Around line 104-247: The responsive utility blocks (sm:, md:, lg:, xl:, 2xl:
prefixed classes) within the media queries are hand-maintained and have become
inconsistent—notice how xl and 2xl blocks have fewer properties than sm, md, and
lg. Instead of manually coding each breakpoint variant, refactor the CSS
generation process to create all responsive blocks programmatically from a
single shared token source. This will ensure all breakpoints remain in lockstep
with the base utilities and prevent future drift between the CSS output and any
serializers that depend on these same token definitions.

In `@resources/js/visual-editor/blocks/_shared/flex-controls/fixtures.json`:
- Around line 3-231: Add a new fixture object to the fixtures array that tests
responsive flex deactivation behavior. The fixture should have a descriptive
name like "responsive-deactivation" and set the input with container.enabled
having base set to true and md set to false. Define the expected output to show
what container classes are generated at the base breakpoint (which should
include the "ap-flex" class) and what reset or deactivation class is generated
at the md breakpoint to prevent regressions when flex is disabled at specific
breakpoints.

In `@resources/js/visual-editor/blocks/group/__tests__/deprecated.test.tsx`:
- Around line 25-29: The test 'ships the upstream five entries plus the `#595`
flex-layout migration' only verifies that the deprecated array has the correct
length, which does not validate the actual content or behavior of the appended
`#595` entry. Strengthen the test by adding assertions that check the appended
migration entry is eligible for layout.type === 'flex', verify the migration
outputs artisanpackFlex.container.enabled.base === true, and confirm the
migration drops the layout.type property. This ensures the migration logic is
functioning correctly, not just that the array has the expected count.
🪄 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: da50b48b-599d-44dd-940b-d07a390cab77

📥 Commits

Reviewing files that changed from the base of the PR and between 0ac562f and 1d8a07f.

📒 Files selected for processing (46)
  • CHANGELOG.md
  • docs/blocks/Flex-Layout.md
  • docs/migration/From-Crosswinds-Blocks.md
  • packages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/grid-item.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/core/column.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/core/columns.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/core/group.blade.php
  • packages/visual-editor-renderer-blade/src/Support/FlexSupport.php
  • packages/visual-editor-renderer-blade/src/VisualEditorRendererBladeServiceProvider.php
  • packages/visual-editor-renderer-blade/tests/Feature/FlexSupportTest.php
  • packages/visual-editor-renderer-react/src/blocks/artisanpack/grid.tsx
  • packages/visual-editor-renderer-react/src/blocks/core/layout.tsx
  • packages/visual-editor-renderer-react/src/support/flex-serializer.ts
  • packages/visual-editor-renderer-react/tests/flex-serializer.test.ts
  • packages/visual-editor-renderer-vue/src/blocks/artisanpack/grid.ts
  • packages/visual-editor-renderer-vue/src/blocks/core/layout.ts
  • packages/visual-editor-renderer-vue/src/support/flex-serializer.ts
  • packages/visual-editor-renderer-vue/tests/flex-serializer.test.ts
  • resources/css/flex-layout.css
  • resources/js/visual-editor/blocks/_shared/flex-controls/__tests__/serializer.test.ts
  • resources/js/visual-editor/blocks/_shared/flex-controls/container-controls.tsx
  • resources/js/visual-editor/blocks/_shared/flex-controls/fixtures.json
  • resources/js/visual-editor/blocks/_shared/flex-controls/index.ts
  • resources/js/visual-editor/blocks/_shared/flex-controls/item-controls.tsx
  • resources/js/visual-editor/blocks/_shared/flex-controls/parent-detector.ts
  • resources/js/visual-editor/blocks/_shared/flex-controls/responsive-helpers.ts
  • resources/js/visual-editor/blocks/_shared/flex-controls/serializer.ts
  • resources/js/visual-editor/blocks/_shared/flex-controls/types.ts
  • resources/js/visual-editor/blocks/column/__tests__/edit.test.tsx
  • resources/js/visual-editor/blocks/column/block.json
  • resources/js/visual-editor/blocks/column/edit.tsx
  • resources/js/visual-editor/blocks/column/save.tsx
  • resources/js/visual-editor/blocks/columns/block.json
  • resources/js/visual-editor/blocks/columns/edit.tsx
  • resources/js/visual-editor/blocks/columns/save.tsx
  • resources/js/visual-editor/blocks/grid-item/block.json
  • resources/js/visual-editor/blocks/grid-item/edit.tsx
  • resources/js/visual-editor/blocks/group/__tests__/deprecated.test.tsx
  • resources/js/visual-editor/blocks/group/__tests__/edit.test.tsx
  • resources/js/visual-editor/blocks/group/block.json
  • resources/js/visual-editor/blocks/group/deprecated.tsx
  • resources/js/visual-editor/blocks/group/edit.tsx
  • resources/js/visual-editor/blocks/group/index.ts
  • resources/js/visual-editor/blocks/group/save.tsx
  • resources/js/visual-editor/blocks/group/suppress-core-flex.ts
  • resources/theme-json/default-base.php

Comment thread docs/blocks/Flex-Layout.md
Comment thread packages/visual-editor-renderer-blade/src/Support/FlexSupport.php
Comment thread packages/visual-editor-renderer-blade/src/Support/FlexSupport.php Outdated
Comment thread resources/js/visual-editor/blocks/column/__tests__/edit.test.tsx
CodeRabbit re-review on PR #596 surfaced six findings; this pass addresses
every one and ports the React/Vue side of the docs claim.

- FlexSupport: hash the ResponsiveCssAccumulator scope key by CSS content
  so two blocks with distinct arbitrary flex values don't dedupe under a
  shared 'flex-arbitrary' bucket.
- FlexSupport: tighten emitNumericClass to PHP regex /^-?\d+$/ so '"1.5"'
  stops collapsing to '1' and emitting a wrong canonical class. Now matches
  the TS serializer's plain-integer-token contract.
- item-controls: switch to a real <fieldset disabled> so non-flex parent
  gating is semantic (keyboard + form submission), not just mouse-blocked.
- serializer: emit 'ap-flex-none' for `enabled: false` overrides so
  `{ base: true, md: false }` actually un-flexes at md+. Mirrored across
  TS / Blade / React / Vue plus the static utility + responsive variants
  in flex-layout.css.
- React/Vue BlockTree: walk the normalized tree, collect arbitraryRules
  via serializeFlex, and emit <style data-ve-flex-arbitrary> -- closes
  the cross-renderer parity gap the docs already promised. Both call
  the shared buildArbitraryStyles which mirrors Blade's grouped
  per-breakpoint @media output.
- column/group edit tests: useState mock now honors the lazy initializer
  semantics that useActiveBreakpointValue relies on.

Iframe canvas fix carried over from the manual-test pass:
- canvas-styles.ts imports flex-layout.css via ?inline and inserts it
  right after LAYOUT_BASELINE_STYLES so the iframe receives the .ap-flex
  utilities (the blocks/*/*.css glob doesn't cover sheets outside
  blocks/). This is what made the editor canvas finally render the
  emitted classes during manual review.

Tests: visual-editor vitest 250/251 files green (the only failing file
is site-editor-app.test.tsx hitting an unrelated window.localStorage
config issue). FlexSupport pest: 15 tests / 30 assertions green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@ViewFromTheBox
ViewFromTheBox marked this pull request as ready for review June 16, 2026 19:08

@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: 5

🤖 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
`@packages/visual-editor-renderer-blade/resources/assets/frontend/flex-layout.css`:
- Around line 120-266: The responsive utility classes at the xl and 2xl
breakpoints are missing many variants that exist in the base, sm, md, and lg
media queries. To fix this, add the missing responsive variants to both the xl
(min-width: 1280px) and 2xl (min-width: 1536px) media query blocks. Complete
these sections by including all the following utility variants with their
appropriate prefixes (xl: and \32xl: respectively): flex-row-reverse,
flex-col-reverse, flex-wrap-reverse, justify-around, justify-evenly,
items-start, items-end, items-baseline, all content-* properties (stretch,
start, end, center, between), all self-* properties (auto, start, end, center,
stretch), flex-grow-2, flex-shrink-0, flex-shrink-1, flex-basis-auto,
flex-basis-full, and order-0, order-1 variants. Match the structure and naming
pattern already established in the sm, md, and lg sections.
- Around line 24-29: The responsive flex layout selectors (sm:ap-flex,
md:ap-flex, lg:ap-flex, xl:ap-flex, and 2xl:ap-flex) are currently applying the
margin-block-start reset globally instead of being scoped to their respective
breakpoint media queries. Move each responsive variant into its corresponding
media query (sm at 640px, md at 768px, lg at 1024px, xl at 1280px, 2xl at
1536px) so that the margin reset only applies when that breakpoint is active.
Keep the base .ap-flex selector outside media queries as it represents the
default mobile-first state. This ensures that blocks using only sm:ap-flex won't
have margin reset applied below the 640px breakpoint, preserving proper mobile
spacing.

In
`@packages/visual-editor-renderer-blade/resources/views/blocks/core/group.blade.php`:
- Around line 27-36: The condition in the foreach loop that checks for
flex-enabled classes is incorrectly treating breakpoint-specific flex classes
(like md:ap-flex) the same as the base flex class ap-flex. This causes the
is-layout-flex class to be applied globally even when flex should only be
enabled at specific breakpoints. Modify the condition that sets $hasFlexEnabled
to true to only check for the base ap-flex class (the exact match), and remove
the str_ends_with check that matches breakpoint-prefixed variants like
md:ap-flex or sm:ap-flex.

In `@packages/visual-editor-renderer-react/src/support/flex-serializer.ts`:
- Around line 371-373: The escapeSelector function in both
packages/visual-editor-renderer-react/src/support/flex-serializer.ts and
packages/visual-editor-renderer-vue/src/support/flex-serializer.ts is missing
the plus character (+) in its regex character class. This causes classes like
ap-gap-x-[calc(100%+1rem)] to generate invalid CSS selectors. Update the regex
pattern in the character class within the escapeSelector function to include the
+ character alongside the existing escaped characters.

In `@resources/js/visual-editor/blocks/column/block.json`:
- Around line 91-95: The responsive-attributes allowlist in the
`artisanpackResponsive.attributes` array is missing the `"artisanpackFlex"`
attribute for flex-enabled blocks. Add `"artisanpackFlex"` to the attributes
array in the `supports.artisanpackResponsive` object for all flex-enabled blocks
(the column block shown here, as well as the columns block and grid-item block)
to ensure the responsive infrastructure treats flex values consistently with
other responsive attributes like spacing and width.
🪄 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: 5e8f7bdd-a3db-4577-a828-961472a4cb36

📥 Commits

Reviewing files that changed from the base of the PR and between 1d8a07f and 9f95c60.

📒 Files selected for processing (25)
  • docs/blocks/Flex-Layout.md
  • packages/visual-editor-renderer-blade/resources/assets/frontend/flex-layout.css
  • packages/visual-editor-renderer-blade/resources/views/blocks/core/group.blade.php
  • packages/visual-editor-renderer-blade/resources/views/components/blocks-styles.blade.php
  • packages/visual-editor-renderer-blade/src/Support/FlexSupport.php
  • packages/visual-editor-renderer-blade/src/View/Components/BlocksStylesComponent.php
  • packages/visual-editor-renderer-react/src/BlockTree.tsx
  • packages/visual-editor-renderer-react/src/support/flex-serializer.ts
  • packages/visual-editor-renderer-vue/src/BlockTree.ts
  • packages/visual-editor-renderer-vue/src/support/flex-serializer.ts
  • resources/css/flex-layout.css
  • resources/js/visual-editor/blocks/_shared/flex-controls/container-controls.tsx
  • resources/js/visual-editor/blocks/_shared/flex-controls/item-controls.tsx
  • resources/js/visual-editor/blocks/_shared/flex-controls/serializer.ts
  • resources/js/visual-editor/blocks/column/__tests__/edit.test.tsx
  • resources/js/visual-editor/blocks/column/block.json
  • resources/js/visual-editor/blocks/column/index.ts
  • resources/js/visual-editor/blocks/columns/block.json
  • resources/js/visual-editor/blocks/columns/index.ts
  • resources/js/visual-editor/blocks/grid-item/block.json
  • resources/js/visual-editor/blocks/grid-item/index.ts
  • resources/js/visual-editor/blocks/group/__tests__/edit.test.tsx
  • resources/js/visual-editor/blocks/group/block.json
  • resources/js/visual-editor/blocks/group/index.ts
  • resources/js/visual-editor/editor/canvas-styles.ts
💤 Files with no reviewable changes (1)
  • resources/js/visual-editor/blocks/_shared/flex-controls/container-controls.tsx
✅ Files skipped from review due to trivial changes (3)
  • resources/js/visual-editor/blocks/grid-item/index.ts
  • resources/js/visual-editor/blocks/group/index.ts
  • docs/blocks/Flex-Layout.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • resources/js/visual-editor/blocks/group/block.json
  • resources/js/visual-editor/blocks/column/tests/edit.test.tsx
  • resources/js/visual-editor/blocks/group/tests/edit.test.tsx
  • packages/visual-editor-renderer-blade/src/Support/FlexSupport.php
  • resources/css/flex-layout.css
  • resources/js/visual-editor/blocks/_shared/flex-controls/serializer.ts

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 5

🤖 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
`@packages/visual-editor-renderer-blade/resources/assets/frontend/flex-layout.css`:
- Around line 120-266: The responsive utility classes at the xl and 2xl
breakpoints are missing many variants that exist in the base, sm, md, and lg
media queries. To fix this, add the missing responsive variants to both the xl
(min-width: 1280px) and 2xl (min-width: 1536px) media query blocks. Complete
these sections by including all the following utility variants with their
appropriate prefixes (xl: and \32xl: respectively): flex-row-reverse,
flex-col-reverse, flex-wrap-reverse, justify-around, justify-evenly,
items-start, items-end, items-baseline, all content-* properties (stretch,
start, end, center, between), all self-* properties (auto, start, end, center,
stretch), flex-grow-2, flex-shrink-0, flex-shrink-1, flex-basis-auto,
flex-basis-full, and order-0, order-1 variants. Match the structure and naming
pattern already established in the sm, md, and lg sections.
- Around line 24-29: The responsive flex layout selectors (sm:ap-flex,
md:ap-flex, lg:ap-flex, xl:ap-flex, and 2xl:ap-flex) are currently applying the
margin-block-start reset globally instead of being scoped to their respective
breakpoint media queries. Move each responsive variant into its corresponding
media query (sm at 640px, md at 768px, lg at 1024px, xl at 1280px, 2xl at
1536px) so that the margin reset only applies when that breakpoint is active.
Keep the base .ap-flex selector outside media queries as it represents the
default mobile-first state. This ensures that blocks using only sm:ap-flex won't
have margin reset applied below the 640px breakpoint, preserving proper mobile
spacing.

In
`@packages/visual-editor-renderer-blade/resources/views/blocks/core/group.blade.php`:
- Around line 27-36: The condition in the foreach loop that checks for
flex-enabled classes is incorrectly treating breakpoint-specific flex classes
(like md:ap-flex) the same as the base flex class ap-flex. This causes the
is-layout-flex class to be applied globally even when flex should only be
enabled at specific breakpoints. Modify the condition that sets $hasFlexEnabled
to true to only check for the base ap-flex class (the exact match), and remove
the str_ends_with check that matches breakpoint-prefixed variants like
md:ap-flex or sm:ap-flex.

In `@packages/visual-editor-renderer-react/src/support/flex-serializer.ts`:
- Around line 371-373: The escapeSelector function in both
packages/visual-editor-renderer-react/src/support/flex-serializer.ts and
packages/visual-editor-renderer-vue/src/support/flex-serializer.ts is missing
the plus character (+) in its regex character class. This causes classes like
ap-gap-x-[calc(100%+1rem)] to generate invalid CSS selectors. Update the regex
pattern in the character class within the escapeSelector function to include the
+ character alongside the existing escaped characters.

In `@resources/js/visual-editor/blocks/column/block.json`:
- Around line 91-95: The responsive-attributes allowlist in the
`artisanpackResponsive.attributes` array is missing the `"artisanpackFlex"`
attribute for flex-enabled blocks. Add `"artisanpackFlex"` to the attributes
array in the `supports.artisanpackResponsive` object for all flex-enabled blocks
(the column block shown here, as well as the columns block and grid-item block)
to ensure the responsive infrastructure treats flex values consistently with
other responsive attributes like spacing and width.
🪄 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: 5e8f7bdd-a3db-4577-a828-961472a4cb36

📥 Commits

Reviewing files that changed from the base of the PR and between 1d8a07f and 9f95c60.

📒 Files selected for processing (25)
  • docs/blocks/Flex-Layout.md
  • packages/visual-editor-renderer-blade/resources/assets/frontend/flex-layout.css
  • packages/visual-editor-renderer-blade/resources/views/blocks/core/group.blade.php
  • packages/visual-editor-renderer-blade/resources/views/components/blocks-styles.blade.php
  • packages/visual-editor-renderer-blade/src/Support/FlexSupport.php
  • packages/visual-editor-renderer-blade/src/View/Components/BlocksStylesComponent.php
  • packages/visual-editor-renderer-react/src/BlockTree.tsx
  • packages/visual-editor-renderer-react/src/support/flex-serializer.ts
  • packages/visual-editor-renderer-vue/src/BlockTree.ts
  • packages/visual-editor-renderer-vue/src/support/flex-serializer.ts
  • resources/css/flex-layout.css
  • resources/js/visual-editor/blocks/_shared/flex-controls/container-controls.tsx
  • resources/js/visual-editor/blocks/_shared/flex-controls/item-controls.tsx
  • resources/js/visual-editor/blocks/_shared/flex-controls/serializer.ts
  • resources/js/visual-editor/blocks/column/__tests__/edit.test.tsx
  • resources/js/visual-editor/blocks/column/block.json
  • resources/js/visual-editor/blocks/column/index.ts
  • resources/js/visual-editor/blocks/columns/block.json
  • resources/js/visual-editor/blocks/columns/index.ts
  • resources/js/visual-editor/blocks/grid-item/block.json
  • resources/js/visual-editor/blocks/grid-item/index.ts
  • resources/js/visual-editor/blocks/group/__tests__/edit.test.tsx
  • resources/js/visual-editor/blocks/group/block.json
  • resources/js/visual-editor/blocks/group/index.ts
  • resources/js/visual-editor/editor/canvas-styles.ts
💤 Files with no reviewable changes (1)
  • resources/js/visual-editor/blocks/_shared/flex-controls/container-controls.tsx
✅ Files skipped from review due to trivial changes (3)
  • resources/js/visual-editor/blocks/grid-item/index.ts
  • resources/js/visual-editor/blocks/group/index.ts
  • docs/blocks/Flex-Layout.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • resources/js/visual-editor/blocks/group/block.json
  • resources/js/visual-editor/blocks/column/tests/edit.test.tsx
  • resources/js/visual-editor/blocks/group/tests/edit.test.tsx
  • packages/visual-editor-renderer-blade/src/Support/FlexSupport.php
  • resources/css/flex-layout.css
  • resources/js/visual-editor/blocks/_shared/flex-controls/serializer.ts
🛑 Comments failed to post (5)
packages/visual-editor-renderer-blade/resources/assets/frontend/flex-layout.css (2)

24-29: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Scope the responsive margin reset to its breakpoint media queries.

sm:/md:/lg:/xl:/2xl: selectors on Line 25–Line 29 are currently global, so a block using only sm:ap-flex still gets margin-block-start: 0 below 640px. That breaks non-flex mobile spacing.

Proposed fix
 :where(.ap-flex) > * + *,
-:where(.sm\:ap-flex) > * + *,
-:where(.md\:ap-flex) > * + *,
-:where(.lg\:ap-flex) > * + *,
-:where(.xl\:ap-flex) > * + *,
-:where(.\32xl\:ap-flex) > * + * { margin-block-start: 0; }
+:where(.sm\:ap-flex) > * + *,
+:where(.md\:ap-flex) > * + *,
+:where(.lg\:ap-flex) > * + *,
+:where(.xl\:ap-flex) > * + *,
+:where(.\32xl\:ap-flex) > * + * { margin-block-start: 0; }
+
+@media (min-width: 640px) { :where(.sm\:ap-flex) > * + * { margin-block-start: 0; } }
+@media (min-width: 768px) { :where(.md\:ap-flex) > * + * { margin-block-start: 0; } }
+@media (min-width: 1024px) { :where(.lg\:ap-flex) > * + * { margin-block-start: 0; } }
+@media (min-width: 1280px) { :where(.xl\:ap-flex) > * + * { margin-block-start: 0; } }
+@media (min-width: 1536px) { :where(.\32xl\:ap-flex) > * + * { margin-block-start: 0; } }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

:where(.ap-flex) > * + * { margin-block-start: 0; }

`@media` (min-width: 640px) { :where(.sm\:ap-flex) > * + * { margin-block-start: 0; } }
`@media` (min-width: 768px) { :where(.md\:ap-flex) > * + * { margin-block-start: 0; } }
`@media` (min-width: 1024px) { :where(.lg\:ap-flex) > * + * { margin-block-start: 0; } }
`@media` (min-width: 1280px) { :where(.xl\:ap-flex) > * + * { margin-block-start: 0; } }
`@media` (min-width: 1536px) { :where(.\32xl\:ap-flex) > * + * { margin-block-start: 0; } }
🤖 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
`@packages/visual-editor-renderer-blade/resources/assets/frontend/flex-layout.css`
around lines 24 - 29, The responsive flex layout selectors (sm:ap-flex,
md:ap-flex, lg:ap-flex, xl:ap-flex, and 2xl:ap-flex) are currently applying the
margin-block-start reset globally instead of being scoped to their respective
breakpoint media queries. Move each responsive variant into its corresponding
media query (sm at 640px, md at 768px, lg at 1024px, xl at 1280px, 2xl at
1536px) so that the margin reset only applies when that breakpoint is active.
Keep the base .ap-flex selector outside media queries as it represents the
default mobile-first state. This ensures that blocks using only sm:ap-flex won't
have margin reset applied below the 640px breakpoint, preserving proper mobile
spacing.

120-266: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Responsive utility coverage is incomplete vs the emitted class contract.

Base utilities include tokens that are missing at responsive levels (especially xl/2xl; e.g., Line 247–Line 257 and Line 260–Line 266 omit many direction/wrap/justify/items/content/self/grow/shrink/basis/order variants present in base). With breakpoint-aware serializer emission, those classes become no-ops at runtime.

🤖 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
`@packages/visual-editor-renderer-blade/resources/assets/frontend/flex-layout.css`
around lines 120 - 266, The responsive utility classes at the xl and 2xl
breakpoints are missing many variants that exist in the base, sm, md, and lg
media queries. To fix this, add the missing responsive variants to both the xl
(min-width: 1280px) and 2xl (min-width: 1536px) media query blocks. Complete
these sections by including all the following utility variants with their
appropriate prefixes (xl: and \32xl: respectively): flex-row-reverse,
flex-col-reverse, flex-wrap-reverse, justify-around, justify-evenly,
items-start, items-end, items-baseline, all content-* properties (stretch,
start, end, center, between), all self-* properties (auto, start, end, center,
stretch), flex-grow-2, flex-shrink-0, flex-shrink-1, flex-basis-auto,
flex-basis-full, and order-0, order-1 variants. Match the structure and naming
pattern already established in the sm, md, and lg sections.
packages/visual-editor-renderer-blade/resources/views/blocks/core/group.blade.php (1)

27-36: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Only force is-layout-flex when base flex is enabled.

Lines 29-35 currently treat breakpoint-only classes (md:ap-flex, etc.) as globally flex-enabled. That forces base display:flex via .is-layout-flex, so mobile can render flex even when flex was intended only for a larger breakpoint.

🔧 Suggested patch
-	$hasFlexEnabled = false;
-	foreach ( $flexClasses as $cls ) {
-		if ( 'ap-flex' === $cls || str_ends_with( $cls, ':ap-flex' ) ) {
-			$hasFlexEnabled = true;
-			break;
-		}
-	}
-	if ( '' === $layoutType && $hasFlexEnabled ) {
+	$hasBaseFlexEnabled = in_array( 'ap-flex', $flexClasses, true );
+	if ( '' === $layoutType && $hasBaseFlexEnabled ) {
 		$layoutClass = 'is-layout-flex';
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

	$hasBaseFlexEnabled = in_array( 'ap-flex', $flexClasses, true );
	if ( '' === $layoutType && $hasBaseFlexEnabled ) {
		$layoutClass = 'is-layout-flex';
	}
🤖 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
`@packages/visual-editor-renderer-blade/resources/views/blocks/core/group.blade.php`
around lines 27 - 36, The condition in the foreach loop that checks for
flex-enabled classes is incorrectly treating breakpoint-specific flex classes
(like md:ap-flex) the same as the base flex class ap-flex. This causes the
is-layout-flex class to be applied globally even when flex should only be
enabled at specific breakpoints. Modify the condition that sets $hasFlexEnabled
to true to only check for the base ap-flex class (the exact match), and remove
the str_ends_with check that matches breakpoint-prefixed variants like
md:ap-flex or sm:ap-flex.
packages/visual-editor-renderer-react/src/support/flex-serializer.ts (1)

371-373: ⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
import re
sample = "md:ap-gap-x-[calc(100%+1rem)]"
pattern = re.compile(r'[\[\]():.,#%/\s]')
escaped = pattern.sub(lambda m: '\\' + m.group(0), sample)
print("sample   :", sample)
print("escaped  :", escaped)
print("plus escaped?", "\\+" in escaped)
PY

rg -n "escapeSelector|replace\\( /\\[\\\\\\[\\\\\\]\\(\\):\\.,#%/\\\\s\\]/g" --type=ts

Repository: ArtisanPack-UI/visual-editor

Length of output: 1199


🏁 Script executed:

# Check the exact content of both files at the mentioned lines
echo "=== React file (lines 369-373) ==="
sed -n '369,373p' packages/visual-editor-renderer-react/src/support/flex-serializer.ts

echo -e "\n=== Vue file (lines 359-363) ==="
sed -n '359,363p' packages/visual-editor-renderer-vue/src/support/flex-serializer.ts

# Look for tests or usages of escapeSelector to understand the impact
echo -e "\n=== Usages of escapeSelector ==="
rg -n "escapeSelector\(" --type=ts -A 2 -B 2

Repository: ArtisanPack-UI/visual-editor

Length of output: 2594


🏁 Script executed:

# Verify if '+' in CSS selectors needs escaping
python3 - <<'PY'
import re

# Test various selectors with unescaped vs escaped '+'
test_cases = [
    "ap-gap-x-[calc(100%+1rem)]",
    "w-[100px+20px]",
    "text-[color:rgb(100+50)]"
]

regex_current = re.compile(r'[\[\]():.,#%/\s]')
regex_fixed = re.compile(r'[\[\]():.,#%/+\s]')

print("Character escaping comparison:\n")
for test in test_cases:
    escaped_current = regex_current.sub(lambda m: '\\' + m.group(0), test)
    escaped_fixed = regex_fixed.sub(lambda m: '\\' + m.group(0), test)
    print(f"Original:  .{test}")
    print(f"Current:   .{escaped_current}")
    print(f"Fixed:     .{escaped_fixed}")
    print(f"Plus escaped? {'+' not in escaped_fixed}")
    print()
PY

Repository: ArtisanPack-UI/visual-editor

Length of output: 503


Add + to the character class in escapeSelector for both React and Vue renderers.

The + character is not currently escaped in the regex pattern. Classes like ap-gap-x-[calc(100%+1rem)] generate invalid CSS selectors without escaping the +, causing those arbitrary rules to fail. Update both packages/visual-editor-renderer-react/src/support/flex-serializer.ts (line 372) and packages/visual-editor-renderer-vue/src/support/flex-serializer.ts (line 362) to include + in the character class:

Suggested patch
- return className.replace( /[\[\]():.,#%/\s]/g, ( char ) => `\\${ char }` )
+ return className.replace( /[\[\]():.,#%/+\s]/g, ( char ) => `\\${ char }` )
🤖 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 `@packages/visual-editor-renderer-react/src/support/flex-serializer.ts` around
lines 371 - 373, The escapeSelector function in both
packages/visual-editor-renderer-react/src/support/flex-serializer.ts and
packages/visual-editor-renderer-vue/src/support/flex-serializer.ts is missing
the plus character (+) in its regex character class. This causes classes like
ap-gap-x-[calc(100%+1rem)] to generate invalid CSS selectors. Update the regex
pattern in the character class within the escapeSelector function to include the
+ character alongside the existing escaped characters.
resources/js/visual-editor/blocks/column/block.json (1)

91-95: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Register artisanpackFlex in the responsive-attributes allowlist for all flex-enabled blocks.

artisanpackFlex is breakpoint-aware, but this list omits it here (and in resources/js/visual-editor/blocks/columns/block.json and resources/js/visual-editor/blocks/grid-item/block.json). Add "artisanpackFlex" to each block’s supports.artisanpackResponsive.attributes array so responsive infrastructure treats flex values consistently with other responsive attributes.

🤖 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/blocks/column/block.json` around lines 91 - 95,
The responsive-attributes allowlist in the `artisanpackResponsive.attributes`
array is missing the `"artisanpackFlex"` attribute for flex-enabled blocks. Add
`"artisanpackFlex"` to the attributes array in the
`supports.artisanpackResponsive` object for all flex-enabled blocks (the column
block shown here, as well as the columns block and grid-item block) to ensure
the responsive infrastructure treats flex values consistently with other
responsive attributes like spacing and width.

Second re-review on PR #596 surfaced five more findings (the inline
comments failed to post due to a GitHub rate limit, so they only landed
in the review summary). Addressed four; deferred one with rationale.

- flex-layout.css: scope each responsive margin-block-start reset
  inside its own @media query. Previously `:where(.sm\:ap-flex) > * + *`
  applied unconditionally, so a block with only `sm:ap-flex` set would
  have its children's `margin-block-start` zeroed even below 640px
  (where the block is still block-flow).
- flex-layout.css: fill out xl + 2xl media queries with the full
  variant set sm/md/lg already define (flex-row-reverse, flex-wrap,
  every justify/items/content/self, grow/shrink/basis/order). The TS
  serializer emits `xl:ap-flex-row-reverse` etc. when the author sets
  those values; without matching CSS the classes were silently dropped.
- group.blade.php: only flip the wrapper to `is-layout-flex` when
  the base `ap-flex` class is present, not when a breakpoint-prefixed
  variant like `md:ap-flex` is present. Prefixed variants mean "flex
  at md+", so flipping unconditionally would apply flex below the
  breakpoint too. The matching `:where(.md\:ap-flex) > * + *` reset
  now lives inside its own media query (see above), so we no longer
  need the wrapper-flip safety net.
- escapeSelector (React + Vue + Blade): add `+` to the escape regex
  so arbitrary values like `ap-gap-x-[calc(100%+1rem)]` produce a
  valid CSS selector instead of breaking on the unescaped `+`.

Deferred (with comment in the PR):
- Adding `"artisanpackFlex"` back to `supports.artisanpackResponsive.attributes`
  on column / columns / grid-item / group block.json. Was intentionally
  removed during manual testing: the responsive HOC mis-routes
  nested-object attributes (artisanpackFlex is a {container, item}
  shape), causing `block-validation` to mark posts as "Block contains
  unexpected or invalid content" on every load. The serializer manages
  its own per-breakpoint cascade internally via the BreakpointRegistry,
  so the attribute does not need to participate in the generic
  responsive-spread infrastructure.

Tests: visual-editor vitest 250/251 files green (same 17 pre-existing
site-editor failures from a window.localStorage config issue unrelated
to this PR). FlexSupport pest: 15 tests / 30 assertions green.

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

Copy link
Copy Markdown
Member Author

Round-three CodeRabbit pass: deferred finding

The latest review (commit b0ef484) addressed four of the five new findings — escape regex + +, scoped responsive margin-reset rules, full xl/2xl variant set, and group.blade.php exact-match check. Commit message has the details.

Deferred: adding "artisanpackFlex" to supports.artisanpackResponsive.attributes on the four block.json files.

This was intentionally removed during manual testing earlier in the PR. The responsive HOC mis-routes nested-object attributes — artisanpackFlex is a { container: { enabled, direction, justifyContent, … }, item: { … } } shape, not the flat-value cascade ({ base, sm, md, … }) the HOC was designed for. With the attribute listed in the allowlist, the editor flagged every post containing flex blocks as "Block contains unexpected or invalid content" on load because the regenerated save output didn't match the persisted markup.

The flex serializer manages its own per-breakpoint cascade internally (via BreakpointRegistry + distinctOverrides), so the attribute doesn't need to participate in the generic responsive-spread infrastructure to get per-breakpoint behavior — every leaf inside container / item is already a ResponsiveAttribute<T> and the serializer walks them directly.

Happy to revisit if a follow-up changes the HOC to handle nested-object attributes; this thread is the breadcrumb.

@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: 1

🤖 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 `@packages/visual-editor-renderer-vue/src/support/flex-serializer.ts`:
- Around line 361-365: The escapeSelector function uses an incomplete whitelist
regex that only escapes specific characters, missing others like asterisk that
appear in arbitrary class values. Replace the whitelist approach with a
blacklist that escapes all characters except those explicitly safe in CSS
identifiers: alphanumeric characters (a-z, A-Z, 0-9), hyphens, and underscores.
Update the regex pattern in escapeSelector to match anything that is NOT in this
safe set, ensuring all CSS-significant characters in arbitrary values like calc
expressions are properly escaped to prevent malformed selectors.
🪄 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: 9e1790f0-d362-456c-92e0-4947799f829d

📥 Commits

Reviewing files that changed from the base of the PR and between 9f95c60 and b0ef484.

📒 Files selected for processing (6)
  • packages/visual-editor-renderer-blade/resources/assets/frontend/flex-layout.css
  • packages/visual-editor-renderer-blade/resources/views/blocks/core/group.blade.php
  • packages/visual-editor-renderer-blade/src/Support/FlexSupport.php
  • packages/visual-editor-renderer-react/src/support/flex-serializer.ts
  • packages/visual-editor-renderer-vue/src/support/flex-serializer.ts
  • resources/css/flex-layout.css
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/visual-editor-renderer-blade/resources/views/blocks/core/group.blade.php
  • resources/css/flex-layout.css
  • packages/visual-editor-renderer-react/src/support/flex-serializer.ts
  • packages/visual-editor-renderer-blade/src/Support/FlexSupport.php

Comment thread packages/visual-editor-renderer-vue/src/support/flex-serializer.ts Outdated
Round-four CodeRabbit follow-up. The allowlist-style escapeSelector keeps
growing as new arbitrary values surface (`+` last round; CodeRabbit's
next example was `*`). Switched all three ports — React, Vue, Blade —
to a denylist that escapes everything outside the CSS-identifier safe
set `[A-Za-z0-9_-]`.

Now arbitrary values that ship with `*`, `~`, `>`, `=`, `&`, `^`, `$`,
`|`, etc. all serialize to a valid selector without revisiting the
regex.

Output is a superset of what the previous allowlists escaped, so the
shared `fixtures.json` parity tests continue to pass — they assert on
the structured `arbitraryRules` shape, not on `buildArbitraryStyles`
output, so the wider escape set doesn't shift the fixtures.

Tests: React + Vue vitest 29/29 files / 429 tests green. FlexSupport
pest 15 tests / 30 assertions green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@ViewFromTheBox
ViewFromTheBox merged commit 08c1a58 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/595-native-flex-layout-controls branch June 16, 2026 19:40
@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: Native flexbox layout controls for group/column/columns/grid-item blocks

1 participant