Skip to content

feat(blocks): fork I3 layout cluster (columns, group/row/stack, buttons, separator, spacer, details) - #478

Merged
ViewFromTheBox merged 2 commits into
release/1.0from
feature/411-layout-cluster
May 28, 2026
Merged

ViewFromTheBox merged 2 commits into
release/1.0from
feature/411-layout-cluster

Conversation

@ViewFromTheBox

@ViewFromTheBox ViewFromTheBox commented May 28, 2026 •

Copy link
Copy Markdown
Member

Description

Forks the 6-source-unit / 8-logical-block layout cluster from core/* → artisanpack/* per docs/plans/13-block-fork.md §2.2.1 + §3: columns + column, group (with row and stack carried as variations), buttons + button, separator, spacer, details. Each fork ships its own block.json, TS-ported edit/save/deprecated/transforms, combined editor + frontend CSS, inline-SVG inserter icon, upstream-state.json baseline, the four-test suite (save / edit / deprecated / transforms), and a Blade view that delegates to the upstream core/* view. React + Vue renderer maps gain artisanpack/{block} entries reusing the existing core component because saved markup is byte-equivalent across the two namespaces.

Pinned upstream: @wordpress/block-library v9.43.0.

Closes: #411

Type of Change

  • New feature (adds new functionality)

Related Issue

Issue: #411
Umbrella: #331
Prerequisite: I0 paragraph pilot (#408), I1 content cluster (#409), I2 media cluster (#410)
Plan: docs/plans/13-block-fork.md §2.2.1

Motivation and Context

Owning the source for every block in the cluster lets us reach customizations that filter hooks alone cannot (saved markup, attribute schema, edit-component structure, deprecations). The fork lands inside 1.0.0 so no host has persisted core/* trees against the editor — there is no migration cost (§2.1).

Changes Made

Per-block forks at resources/js/visual-editor/blocks/{name}/:

  • separator — single v1 deprecation entry; bidirectional core↔artisanpack + upstream input/raw transforms preserved.
  • spacer — v1 numeric→px-string migration; ResizableBox edit; diverges from upstream private/unlocked APIs (spacing presets resolved via a CSS-var helper, not useSpacingSizes).
  • details — no upstream deprecations to carry; InnerBlocks-based <details><summary> body.
  • buttons + button — allowedBlocks rewired to artisanpack/button; button/edit.tsx uses the public __experimentalLinkControl inside a Popover (not the upstream private-API unlock flow). Deprecation chain trimmed from 12 → 2 entries (legacy width % + v1 customColor migration); older saves need a follow-up CLI migration if a host has persisted them.
  • columns + column — allowedBlocks / parent / variation innerBlocks templates all rewired to artisanpack/*. Inline SVG variation icons (50/50, 33/66, etc.) replace @wordpress/components Path/SVG.
  • group — variations.ts carries group + row + stack forward. Upstream grid variation excluded (it splits into a standalone artisanpack/grid + artisanpack/grid-item pair at a separate stage per plan §2.2.1). edit.tsx swaps the private HTMLElementControl for a plain SelectControl.

Plumbing:

  • editor/custom-blocks.ts now threads a variations field through the auto-discovery pipeline so the columns + group variation pickers render selectable tiles. Without this they'd render the bare "Skip" link and no layout choices.
  • editor/__tests__/custom-blocks.test.tsx adds privateApis + spacing-sizes stubs to the central @wordpress/block-editor mock so the new forks import cleanly under vitest.
  • config/visual-editor.php adds the 10 artisanpack/* layout names to the enabled_blocks allow-list alongside their core/* siblings.
  • packages/renderer-parity.json lists every new artisanpack/* block.
  • packages/visual-editor-renderer-react/src/blocks/registerCoreBlocks.ts + the Vue equivalent gain artisanpack/{block} entries reusing the existing core components.
  • packages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/{block}.blade.php for all 10 names — each @includes the matching core/* Blade template.

How Has This Been Tested?

Testing Environment:

  • Operating System: macOS (Darwin 25.5.0)
  • Browser: Chrome (Laravel Herd local)
  • PHP Version: 8.4.3
  • Project Version: release/1.0 of artisanpack-ui/visual-editor

Tests Performed:

  1. npm test -- --run — full vitest suite: 191 files / 1,359 tests passing.
  2. npm run verify:parity — manifest check: blade / react / vue all register every block.
  3. ./vendor/bin/pest — full pest suite: 644 passing, 1 pre-existing failure (BlocksControllerTest expects artisanpack/paragraph server-side; pre-dates this branch on release/1.0).
  4. Manual testing in ~/Herd/jmwd-keystone-cms after rebuilding bundles and syncing dist/editor/ → public/visual-editor/:
    • All 10 new blocks appear in the inserter alongside their core/* siblings.
    • artisanpack/spacer renders + resizes without crashing.
    • artisanpack/button exposes the proper link icon in the toolbar; clicking opens a Popover-anchored LinkControl (not window.prompt); unlinking clears url / rel / linkTarget cleanly.
    • artisanpack/columns + artisanpack/group placeholders render their variation tiles (50/50, 33/66, row, stack, etc.).

Accessibility Tests Run

  • Keyboard navigation tested (button link picker opens via toolbar focus + closes on Esc; variation picker arrow-keys through tiles)
  • Screen reader tested — not in scope for this PR; tracked under the umbrella accessibility audit for Umbrella: Port core blocks into visual-editor package as artisanpack/* #331
  • Color contrast verified — no new colors introduced; styles vendored from upstream
  • ARIA labels checked — toolbar buttons carry title; placeholder picker labels passed through to __experimentalBlockVariationPicker

Tests Added

  • Unit tests added — per-block save / edit / deprecated / transforms tests under each block's __tests__/
  • Integration tests added — group adds variations.test.ts asserting row + stack are exported and grid is not
  • All tests passing (1,359 / 1,359 vitest)

Test details: Each block ships a save snapshot covering attribute → markup, an edit smoke render, a deprecation chain assertion (length + per-entry migrate / save), and a transforms test covering both directions including the new bidirectional core/X ↔ artisanpack/X block transforms.

Documentation

  • Inline code documentation added — every fork file carries a header docblock identifying the upstream subpath, version, and a per-block upstream-state.json enumerates port status + divergences per file.
  • README / Wiki updated — not in scope for individual cluster PRs; the umbrella Umbrella: Port core blocks into visual-editor package as artisanpack/* #331 tracks ecosystem-level docs.

Documentation details: upstream-state.json per block is the canonical drift record consumed by scripts/upstream-diff.mjs; CI will surface drift against the pinned upstream version going forward.

Screenshots

Manual smoke screenshots captured during the manual-test pause — placeholder tiles render in columns/group; button toolbar shows the link icon + Popover.

Pre-Submission Checklist

  • Followed contributing guidelines
  • Checked for other open PRs for same update
  • Code passes all tests (1,359 vitest passing; 644/645 pest with 1 pre-existing unrelated failure)
  • Code has been linted (CodeRabbit reviewed twice locally; actionable findings addressed)
  • Accessibility tests completed (keyboard + ARIA; SR deferred per umbrella)
  • Code follows project style guide
  • Self-review completed
  • Comments added for complex code (private-API divergences, variation-grid exclusion, etc.)
  • No new warnings generated

Summary by CodeRabbit

  • New Features
    • Added a suite of visual-editor blocks: Button, Buttons, Column, Columns, Details, Group, Row, Separator, Spacer.
    • Each block includes editor UI, save output, styles, inserter icons, and predefined variations.
    • Enabled round‑trip transforms so these blocks convert to/from the corresponding core blocks for seamless migration.
    • Visual editor now recognizes the new artisanpack layout blocks for rendering and insertion.

Review Change Stack

Forks the 6-source-unit layout cluster from `core/*` → `artisanpack/*` per
docs/plans/13-block-fork.md §2.2.1 + §3: columns + column, group (with row
and stack as variations), buttons + button, separator, spacer, details.

Each fork ships its own block.json, TS-ported edit/save/deprecated/
transforms, combined editor + frontend CSS, inline-SVG inserter icon,
upstream-state.json baseline, the four-test suite (save / edit /
deprecated / transforms), and a Blade view that delegates to the upstream
core/* view. React + Vue renderer maps gain artisanpack/{block} entries
reusing the existing core component because saved markup is
byte-equivalent across the two namespaces.

Notable customizations (documented per-block in upstream-state.json):

- artisanpack/columns + column rewire `allowedBlocks` / `parent` / the
  variations' `innerBlocks` templates to the artisanpack/* namespace so
  the parent only accepts forked children.
- artisanpack/buttons applies the same allowedBlocks swap to point at
  artisanpack/button.
- artisanpack/group's variations carry forward group + row + stack but
  exclude the upstream `grid` variation (split into a standalone
  artisanpack/grid + artisanpack/grid-item pair at a separate stage).
- Each block adds bidirectional `core/X` ↔ `artisanpack/X` block
  transforms so mixed documents round-trip during the V1 rollout.

Plumbing changes:

- editor/custom-blocks.ts now threads a `variations` field through the
  auto-discovery pipeline so the columns + group variation pickers render
  selectable tiles (would otherwise drop on the destructure floor).
- config/visual-editor.php adds the 10 artisanpack/* layout names to the
  `enabled_blocks` allow-list alongside their core/* siblings.
- packages/renderer-parity.json lists every new artisanpack/* block so the
  blade/react/vue parity check passes.

Pinned upstream: `@wordpress/block-library` v9.43.0.

Closes #411
@coderabbitai

coderabbitai Bot commented May 28, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

<review_stack_artifact>

</review_stack_artifact>

✨ 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/411-layout-cluster

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

🧹 Nitpick comments (2)
resources/js/visual-editor/blocks/button/button.css (1)

93-93: 💤 Low value

Normalize CSS keyword casing to lowercase.

Per CSS conventions and stylelint rules, color keywords should be lowercase (currentcolor instead of currentColor). While browsers are case-insensitive, following the convention improves consistency.

♻️ Proposed fix
 :root :where(.wp-block-button.is-style-outline > .wp-block-button__link),
 :root :where(.wp-block-button .wp-block-button__link.is-style-outline) {
-    border: 2px solid currentColor;
+    border: 2px solid currentcolor;
     padding: 0.667em 1.333em;
 }

 :root :where(.wp-block-button.is-style-outline > .wp-block-button__link:not(.has-text-color)),
 :root :where(.wp-block-button .wp-block-button__link.is-style-outline:not(.has-text-color)) {
-    color: currentColor;
+    color: currentcolor;
 }

Also applies to: 99-99

🤖 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/button/button.css` at line 93, Replace the
mixed-case CSS color keyword by changing the border declaration value from
currentColor to lowercase currentcolor in the button CSS rule (the line with
"border: 2px solid currentColor;"); update any other occurrences (e.g., the one
at the other noted location) so all uses of the CSS keyword use lowercase
"currentcolor" for consistency with stylelint and conventions.
resources/js/visual-editor/blocks/details/__tests__/deprecated.test.tsx (1)

22-23: ⚖️ Poor tradeoff

Regex patterns are fragile to formatting changes.

The regex patterns will break if the deprecated constant is declared with different spacing or on multiple lines. Since this test is documented as avoiding heavy imports for verification, the trade-off is acceptable. However, consider whether a simple import-and-assert approach would be more maintainable:

import { deprecated } from '../index';
expect(deprecated).toEqual([]);
🤖 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/details/__tests__/deprecated.test.tsx`
around lines 22 - 23, The current test uses fragile regexes to assert the
presence and export of the deprecated constant; replace the regex-based checks
with a direct import and value assertion: import the exported symbol named
deprecated from the module under test (the index export referenced in the test)
and assert expect(deprecated).toEqual([]) so the test verifies the actual
exported value rather than relying on formatting-sensitive regex patterns.
🤖 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/button/button.css`:
- Line 14: Replace the deprecated CSS declaration "word-break: break-word" with
the modern standard "overflow-wrap: break-word" in the button.css rule;
optionally keep "word-break: normal" before or after for legacy behavior if you
need explicit word-break fallback, but remove the deprecated value and use
overflow-wrap to ensure standards-compliant wrapping.

In `@resources/js/visual-editor/blocks/button/edit.tsx`:
- Around line 188-193: When updating link settings in the onChange handler,
avoid overwriting a user-provided rel or leaving stale "noreferrer noopener"
tokens: build the new rel by starting from next.rel if provided, otherwise start
from the current attributes.rel, then if next.opensInNewTab is true ensure
"noreferrer" and "noopener" are present (append if missing), and if
next.opensInNewTab is false remove only the "noreferrer" and "noopener" tokens
but preserve any other tokens; finally call setAttributes with url, linkTarget
and the computed rel (typed as Partial<ButtonAttributes>) so you preserve and
normalize rel rather than blindly replacing it.

In `@resources/js/visual-editor/blocks/button/upstream-state.json`:
- Around line 20-21: Update the "fork" metadata entry for "edit.tsx" in
upstream-state.json to reflect the current implementation: remove the stale
"window.prompt-based ToolbarButton" description and instead note that the fork
uses a Popover-anchored LinkControl (Popover + LinkControl flow), a
SelectControl for tagName, TextControl for rel, applies width via
getWidthClasses, and drops createPageEntity / LinkControl suggestion flow; also
update the corresponding duplicate entry mentioned at lines 33-34 so both
summaries match the actual edit.tsx behavior.

In `@resources/js/visual-editor/blocks/columns/deprecated.tsx`:
- Around line 24-29: The code uses doc.body.firstChild which can be a text node
and causes accessing classList to throw; change the selection to an element node
(e.g., use doc.body.firstElementChild or iterate childNodes and pick nodeType
=== 1) and cast to HTMLElement before accessing classList in the block where
firstChild is declared; ensure the variable used later (firstChild) is an
HTMLElement | null and preserve the existing loop that matches classListItem
against /^layout-column-(\d+)$/ (columnMatch).

In `@resources/js/visual-editor/blocks/columns/transforms.ts`:
- Around line 115-128: The transform handlers currently pass innerBlocks through
unchanged which can leave child blocks named core/column inside
artisanpack/columns (and vice versa); update the transform functions that call
createBlock(name, attributes, innerBlocks) and createBlock('core/columns',
attributes, innerBlocks) to remap innerBlocks before passing them in by mapping
each child block and replacing its name/blockName to the corresponding forked
child (e.g. when creating artisanpack/columns, convert any 'core/column'
children to 'artisanpack/column', and when creating core/columns convert
'artisanpack/column' back to 'core/column') so parent/child names remain
consistent during round-trips.

In `@resources/js/visual-editor/blocks/details/edit.tsx`:
- Around line 105-109: The inspector toggle updates showContent but leaves
isOpen stale; update any setAttributes calls that change showContent (e.g., the
resetAll callback and the other two occurrences) to also set isOpen to the same
boolean value so the editor open state stays in sync (use setAttributes({
showContent: false/true, isOpen: false/true }) or propagate the toggle value
variable for both keys).
- Line 156: The edit component currently forces an empty string for the details
`name` attribute via `name={name || ''}`, which renders `name=""` when unset;
change the rendering to omit the attribute when `name` is falsy (e.g.,
`name={name || undefined}` or conditional prop spread) so the edit component
matches the save component behavior and avoids grouping unnamed <details>
blocks; update the JSX in the edit component where `name={name || ''}` is used
(in the Details block edit component) to only pass the `name` prop when a real
value exists.

In `@resources/js/visual-editor/blocks/details/save.tsx`:
- Around line 9-15: The save component currently hardcodes the fallback summary
string 'Details' which prevents localization; import the translation helper __
from '`@wordpress/i18n`' and replace the hardcoded fallback with __('Details',
'your-text-domain') where the summary fallback is used (look for the fallback in
the save function/JSX around the RichText/summary usage in save.tsx and update
the import line to include __). Ensure you pass your block/text domain string to
__() so the default label is localizable.

In `@resources/js/visual-editor/blocks/details/transforms.ts`:
- Around line 42-43: The code is masking a real type mismatch by casting
cloneBlock to any; update typings so cloneBlock is called with a proper
`@wordpress/blocks` Block instead of the custom BlockShape: import and use the
Block type from '`@wordpress/blocks`' (or make BlockShape extend/convert to that
Block), change the transform parameter types (and any helper signatures that
accept blocks) to accept Block, and replace all three casts of cloneBlock as any
with direct calls to cloneBlock(block) using the correct type; ensure
innerBlocks/attributes match the Block shape or convert BlockShape objects into
a valid Block before calling cloneBlock.

In `@resources/js/visual-editor/blocks/group/group.css`:
- Line 57: The CSS uses the keyword "currentColor" which breaks the
value-keyword-case lint rule; update the declaration in group.css (the rule that
contains "border: 1px dashed currentColor;") to use the lowercase keyword
"currentcolor" and run lint; also search the file for any other occurrences of
"currentColor" and normalize them to "currentcolor".

In `@resources/js/visual-editor/blocks/group/placeholder.tsx`:
- Around line 85-95: The placeholder hide logic in the useEffect currently
treats grid like flex during initialization but only checks usedLayoutType ===
'flex' when deciding to call setShowPlaceholder(false); update the effect to
also consider the grid layout by including usedLayoutType === 'grid' in the
condition (i.e., treat both 'flex' and 'grid' the same), keeping the other
checks (hasInnerBlocks, backgroundColor, fontSize, textColor, style) intact so
setShowPlaceholder is cleared when any of those conditions or either layout is
present.

In `@resources/js/visual-editor/blocks/separator/separator.css`:
- Line 11: Update CSS to satisfy stylelint: change any occurrences of the color
keyword casing from currentColor to currentcolor (e.g., the border-top: 2px
solid currentColor declaration) and add the required space inside the inline
comment so it reads with a leading space (e.g., change /*no-select*/ to /*
no-select */) so the rules for keyword-case and comment-whitespace-inside are
respected.

In `@resources/js/visual-editor/blocks/spacer/spacer.css`:
- Around line 23-27: The CSS rule contains intra-rule blank lines before
declarations (between width/min-height and min-width/height) which violates
Stylelint's declaration-empty-line-before; remove the empty lines so the
declarations are contiguous (e.g., place width: 100%; min-height: 8px;
min-width: 8px; height: 100%; inside the same rule without blank lines), then
re-run linting to confirm the rule passes.

---

Nitpick comments:
In `@resources/js/visual-editor/blocks/button/button.css`:
- Line 93: Replace the mixed-case CSS color keyword by changing the border
declaration value from currentColor to lowercase currentcolor in the button CSS
rule (the line with "border: 2px solid currentColor;"); update any other
occurrences (e.g., the one at the other noted location) so all uses of the CSS
keyword use lowercase "currentcolor" for consistency with stylelint and
conventions.

In `@resources/js/visual-editor/blocks/details/__tests__/deprecated.test.tsx`:
- Around line 22-23: The current test uses fragile regexes to assert the
presence and export of the deprecated constant; replace the regex-based checks
with a direct import and value assertion: import the exported symbol named
deprecated from the module under test (the index export referenced in the test)
and assert expect(deprecated).toEqual([]) so the test verifies the actual
exported value rather than relying on formatting-sensitive regex patterns.
🪄 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: 5e4ea03c-4915-4128-91d5-2115c067ba05

📥 Commits

Reviewing files that changed from the base of the PR and between 49d5a03 and 52fcee4.

📒 Files selected for processing (130)
  • config/visual-editor.php
  • packages/renderer-parity.json
  • packages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/button.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/buttons.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/column.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/columns.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/details.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/group.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/row.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/separator.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/spacer.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/artisanpack/stack.blade.php
  • packages/visual-editor-renderer-react/src/blocks/registerCoreBlocks.ts
  • packages/visual-editor-renderer-vue/src/blocks/registerCoreBlocks.ts
  • resources/js/visual-editor/blocks/button/__tests__/deprecated.test.tsx
  • resources/js/visual-editor/blocks/button/__tests__/edit.test.tsx
  • resources/js/visual-editor/blocks/button/__tests__/save.test.tsx
  • resources/js/visual-editor/blocks/button/__tests__/transforms.test.ts
  • resources/js/visual-editor/blocks/button/block.json
  • resources/js/visual-editor/blocks/button/button.css
  • resources/js/visual-editor/blocks/button/constants.ts
  • resources/js/visual-editor/blocks/button/deprecated.tsx
  • resources/js/visual-editor/blocks/button/edit.tsx
  • resources/js/visual-editor/blocks/button/get-updated-link-attributes.ts
  • resources/js/visual-editor/blocks/button/index.ts
  • resources/js/visual-editor/blocks/button/inserter-icon.tsx
  • resources/js/visual-editor/blocks/button/save.tsx
  • resources/js/visual-editor/blocks/button/transforms.ts
  • resources/js/visual-editor/blocks/button/upstream-state.json
  • resources/js/visual-editor/blocks/button/utils.ts
  • resources/js/visual-editor/blocks/buttons/__tests__/deprecated.test.tsx
  • resources/js/visual-editor/blocks/buttons/__tests__/edit.test.tsx
  • resources/js/visual-editor/blocks/buttons/__tests__/save.test.tsx
  • resources/js/visual-editor/blocks/buttons/__tests__/transforms.test.ts
  • resources/js/visual-editor/blocks/buttons/block.json
  • resources/js/visual-editor/blocks/buttons/buttons.css
  • resources/js/visual-editor/blocks/buttons/deprecated.tsx
  • resources/js/visual-editor/blocks/buttons/edit.tsx
  • resources/js/visual-editor/blocks/buttons/index.ts
  • resources/js/visual-editor/blocks/buttons/inserter-icon.tsx
  • resources/js/visual-editor/blocks/buttons/save.tsx
  • resources/js/visual-editor/blocks/buttons/transforms.ts
  • resources/js/visual-editor/blocks/buttons/upstream-state.json
  • resources/js/visual-editor/blocks/column/__tests__/deprecated.test.tsx
  • resources/js/visual-editor/blocks/column/__tests__/edit.test.tsx
  • resources/js/visual-editor/blocks/column/__tests__/save.test.tsx
  • resources/js/visual-editor/blocks/column/__tests__/transforms.test.ts
  • resources/js/visual-editor/blocks/column/block.json
  • resources/js/visual-editor/blocks/column/column.css
  • resources/js/visual-editor/blocks/column/deprecated.tsx
  • resources/js/visual-editor/blocks/column/edit.tsx
  • resources/js/visual-editor/blocks/column/index.ts
  • resources/js/visual-editor/blocks/column/inserter-icon.tsx
  • resources/js/visual-editor/blocks/column/save.tsx
  • resources/js/visual-editor/blocks/column/transforms.ts
  • resources/js/visual-editor/blocks/column/upstream-state.json
  • resources/js/visual-editor/blocks/columns/__tests__/deprecated.test.tsx
  • resources/js/visual-editor/blocks/columns/__tests__/edit.test.tsx
  • resources/js/visual-editor/blocks/columns/__tests__/save.test.tsx
  • resources/js/visual-editor/blocks/columns/__tests__/transforms.test.ts
  • resources/js/visual-editor/blocks/columns/block.json
  • resources/js/visual-editor/blocks/columns/columns.css
  • resources/js/visual-editor/blocks/columns/deprecated.tsx
  • resources/js/visual-editor/blocks/columns/edit.tsx
  • resources/js/visual-editor/blocks/columns/index.ts
  • resources/js/visual-editor/blocks/columns/inserter-icon.tsx
  • resources/js/visual-editor/blocks/columns/save.tsx
  • resources/js/visual-editor/blocks/columns/transforms.ts
  • resources/js/visual-editor/blocks/columns/upstream-state.json
  • resources/js/visual-editor/blocks/columns/utils.ts
  • resources/js/visual-editor/blocks/columns/variations.tsx
  • resources/js/visual-editor/blocks/details/__tests__/deprecated.test.tsx
  • resources/js/visual-editor/blocks/details/__tests__/edit.test.tsx
  • resources/js/visual-editor/blocks/details/__tests__/save.test.tsx
  • resources/js/visual-editor/blocks/details/__tests__/transforms.test.ts
  • resources/js/visual-editor/blocks/details/block.json
  • resources/js/visual-editor/blocks/details/details.css
  • resources/js/visual-editor/blocks/details/edit.tsx
  • resources/js/visual-editor/blocks/details/index.ts
  • resources/js/visual-editor/blocks/details/inserter-icon.tsx
  • resources/js/visual-editor/blocks/details/save.tsx
  • resources/js/visual-editor/blocks/details/transforms.ts
  • resources/js/visual-editor/blocks/details/upstream-state.json
  • 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/__tests__/save.test.tsx
  • resources/js/visual-editor/blocks/group/__tests__/transforms.test.ts
  • resources/js/visual-editor/blocks/group/__tests__/variations.test.ts
  • 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/group.css
  • resources/js/visual-editor/blocks/group/index.ts
  • resources/js/visual-editor/blocks/group/inserter-icon.tsx
  • resources/js/visual-editor/blocks/group/placeholder.tsx
  • resources/js/visual-editor/blocks/group/save.tsx
  • resources/js/visual-editor/blocks/group/transforms.ts
  • resources/js/visual-editor/blocks/group/upstream-state.json
  • resources/js/visual-editor/blocks/group/variations.ts
  • resources/js/visual-editor/blocks/separator/__tests__/deprecated.test.tsx
  • resources/js/visual-editor/blocks/separator/__tests__/edit.test.tsx
  • resources/js/visual-editor/blocks/separator/__tests__/save.test.tsx
  • resources/js/visual-editor/blocks/separator/__tests__/transforms.test.ts
  • resources/js/visual-editor/blocks/separator/block.json
  • resources/js/visual-editor/blocks/separator/deprecated.tsx
  • resources/js/visual-editor/blocks/separator/edit.tsx
  • resources/js/visual-editor/blocks/separator/index.ts
  • resources/js/visual-editor/blocks/separator/inserter-icon.tsx
  • resources/js/visual-editor/blocks/separator/save.tsx
  • resources/js/visual-editor/blocks/separator/separator.css
  • resources/js/visual-editor/blocks/separator/transforms.ts
  • resources/js/visual-editor/blocks/separator/upstream-state.json
  • resources/js/visual-editor/blocks/separator/use-deprecated-opacity.ts
  • resources/js/visual-editor/blocks/spacer/__tests__/deprecated.test.tsx
  • resources/js/visual-editor/blocks/spacer/__tests__/edit.test.tsx
  • resources/js/visual-editor/blocks/spacer/__tests__/save.test.tsx
  • resources/js/visual-editor/blocks/spacer/__tests__/transforms.test.ts
  • resources/js/visual-editor/blocks/spacer/block.json
  • resources/js/visual-editor/blocks/spacer/constants.ts
  • resources/js/visual-editor/blocks/spacer/controls.tsx
  • resources/js/visual-editor/blocks/spacer/deprecated.tsx
  • resources/js/visual-editor/blocks/spacer/edit.tsx
  • resources/js/visual-editor/blocks/spacer/index.ts
  • resources/js/visual-editor/blocks/spacer/inserter-icon.tsx
  • resources/js/visual-editor/blocks/spacer/save.tsx
  • resources/js/visual-editor/blocks/spacer/spacer.css
  • resources/js/visual-editor/blocks/spacer/transforms.ts
  • resources/js/visual-editor/blocks/spacer/upstream-state.json
  • resources/js/visual-editor/editor/__tests__/custom-blocks.test.tsx
  • resources/js/visual-editor/editor/custom-blocks.ts

Comment thread resources/js/visual-editor/blocks/button/button.css Outdated
Comment thread resources/js/visual-editor/blocks/button/edit.tsx
Comment thread resources/js/visual-editor/blocks/button/upstream-state.json Outdated
Comment thread resources/js/visual-editor/blocks/columns/deprecated.tsx Outdated
Comment thread resources/js/visual-editor/blocks/columns/transforms.ts
Comment on lines +42 to +43
// eslint-disable-next-line @typescript-eslint/no-explicit-any
blocks.map((block) => (cloneBlock as any)(block))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if other blocks in this PR use the same pattern
rg -n "cloneBlock as any" --type=ts --type=tsx

# Check the actual cloneBlock type signature
rg -A5 "export.*cloneBlock" node_modules/@wordpress/blocks/build-types/

Repository: ArtisanPack-UI/visual-editor

Length of output: 184


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect transforms.ts around the reported lines
sed -n '1,140p' resources/js/visual-editor/blocks/details/transforms.ts | nl -ba | sed -n '1,140p'

# 2) Find all occurrences of the cast pattern in this repo
rg -n "cloneBlock as any" -S --glob='*.ts' --glob='*.tsx' .

Repository: ArtisanPack-UI/visual-editor

Length of output: 115


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Show transforms.ts around the reported lines (no nl dependency)
sed -n '30,90p' resources/js/visual-editor/blocks/details/transforms.ts

# 2) Find all occurrences of the cast pattern in the repo
rg -n "cloneBlock as any" -S --glob='*.ts' --glob='*.tsx' .

# 3) Locate the TS declaration/typing for cloneBlock in installed `@wordpress/blocks`
#    (search for "cloneBlock" in any .d.ts/.ts/.js under the package)
rg -n "cloneBlock" node_modules/@wordpress/blocks --hidden --glob='*.d.ts' --glob='*.ts' --glob='*.tsx' --glob='*.js' --glob='*.jsx' --glob='*.mjs' --glob='*.cjs' | head -n 50

# 4) If we found a declaration file, dump the relevant section (best-effort: search for "function cloneBlock" or "declare const cloneBlock")
#    This is conditional: run only if rg found something under build-types/build-module/types.
echo "---- candidate declarations (best-effort) ----"
rg -n "declare (function|const) cloneBlock|function cloneBlock|const cloneBlock" node_modules/@wordpress/blocks --hidden --glob='*.d.ts' | head -n 50

Repository: ArtisanPack-UI/visual-editor

Length of output: 2287


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find where `@wordpress/blocks` is referenced (version)
rg -n "\"`@wordpress/blocks`\"" package.json pnpm-lock.yaml yarn.lock package-lock.json 2>/dev/null || true

# Show package.json deps section for context
cat package.json

# Confirm whether node_modules exists in this sandbox
ls -la node_modules/@wordpress 2>/dev/null || echo "no node_modules/@wordpress in sandbox"

Repository: ArtisanPack-UI/visual-editor

Length of output: 3878


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,120p' resources/js/visual-editor/blocks/details/transforms.ts

Repository: ArtisanPack-UI/visual-editor

Length of output: 2469


🌐 Web query:

@wordpress/blocks 15.16.0 cloneBlock TypeScript type definition

💡 Result:

In @wordpress/blocks (15.16.0), cloneBlock’s TypeScript type signature is: export function cloneBlock( block: Block, mergeAttributes?: Record< string, unknown >, newInnerBlocks?: Block[]): Block; This matches the @wordpress/blocks package documentation for cloneBlock (its parameters are described as a block instance, optional mergeAttributes as “Block attributes” and optional newInnerBlocks as “Nested blocks”).[1][2][3] Note: If you’re using TypeScript and need the exact exported types (e.g., Block vs BlockInstance naming), install @wordpress/blocks itself, since the package provides its own type definitions and the legacy DefinitelyTyped stub is deprecated.[4][1]

Citations:


🌐 Web query:

site:github.com "declare function cloneBlock" "@wordpress/blocks" "cloneBlock("

💡 Result:

The cloneBlock function is part of the @wordpress/blocks package in the WordPress block editor (Gutenberg) environment [1][2]. It is used to create a copy of a block object [1][2]. Description The cloneBlock function returns a new copy of a given block instance [1][2]. It supports optional arguments to merge new attributes into the cloned block or to replace its original inner blocks [1][2]. Signature cloneBlock( block, mergeAttributes, newInnerBlocks) Parameters block (Object): The original block instance to be cloned [1][2]. mergeAttributes (Object, optional): An object containing attributes to be merged into the cloned block [1][2]. newInnerBlocks (Array, optional): An array of nested blocks to replace the cloned block's inner blocks [1][2]. Returns Object: Returns the newly created, cloned block object [1][2]. Usage Context This function is frequently used in scenarios where developers need to programmatically duplicate blocks, such as when implementing custom block actions [3]. It is typically used alongside other block-related functions from the WordPress data stores, such as getting existing blocks (e.g., getBlocksByClientId) and inserting the new copy back into the editor (e.g., insertBlocks or insertBlock) [3].

Citations:


🌐 Web query:

@wordpress/blocks cloneBlock (block: ) => cloneBlock types d.ts

💡 Result:

The cloneBlock function is part of the @wordpress/blocks package and is used to create a copy of a block object, with the option to merge new attributes or replace its inner blocks [1][2]. While the WordPress project has worked to improve TypeScript support, the official @wordpress/blocks package does not always provide comprehensive, out-of-the-box type definitions for every internal API [3]. Developers often rely on the @wordpress/blocks package types where available, or community-maintained definitions [4][3]. The function signature for cloneBlock is generally defined as follows [1][2]: cloneBlock( block: BlockInstance, mergeAttributes?: Record<string, unknown>, newInnerBlocks?: BlockInstance[]): BlockInstance In this signature: - block: The original block instance to be cloned [1]. - mergeAttributes: (Optional) An object containing attributes to merge into the cloned block [1]. - newInnerBlocks: (Optional) An array of block instances to replace the original inner blocks [1]. If you are working in a TypeScript environment, you can typically import the necessary types from @wordpress/blocks [4]: import { cloneBlock, BlockInstance } from '@wordpress/blocks'; If specific type definitions are missing or outdated in your local environment, you may need to define an interface for your block instances or rely on the broader BlockInstance type provided by the package [4][3].

Citations:


Replace cloneBlock as any with proper WordPress block typing
cloneBlock in @wordpress/blocks@15.16.0 is typed as cloneBlock(block: Block, ...), but this file passes a custom BlockShape (name/attributes/innerBlocks only) into cloneBlock, so the as any casts at lines 42-43, 58-58, and 73-74 are masking a real type mismatch. Align the types instead (e.g., type BlockShape/transform parameters using @wordpress/blocks’ Block type, or convert BlockShape to Block), and drop the any casts.

🤖 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/details/transforms.ts` around lines 42 -
43, The code is masking a real type mismatch by casting cloneBlock to any;
update typings so cloneBlock is called with a proper `@wordpress/blocks` Block
instead of the custom BlockShape: import and use the Block type from
'`@wordpress/blocks`' (or make BlockShape extend/convert to that Block), change
the transform parameter types (and any helper signatures that accept blocks) to
accept Block, and replace all three casts of cloneBlock as any with direct calls
to cloneBlock(block) using the correct type; ensure innerBlocks/attributes match
the Block shape or convert BlockShape objects into a valid Block before calling
cloneBlock.

Comment thread resources/js/visual-editor/blocks/group/group.css Outdated
Comment thread resources/js/visual-editor/blocks/group/placeholder.tsx
Comment thread resources/js/visual-editor/blocks/separator/separator.css Outdated
Comment thread resources/js/visual-editor/blocks/spacer/spacer.css
- button/edit.tsx: route LinkControl onChange through getUpdatedLinkAttributes
  so rel + linkTarget stay in sync on tab toggles (no more stale
  `noreferrer noopener` after unchecking "open in new tab").
- buttons/transforms.ts + columns/transforms.ts: remap child block names
  recursively on core↔artisanpack round-trips so artisanpack/buttons can't
  end up holding core/button children (and vice versa).
- columns/deprecated.tsx: switch from doc.body.firstChild to
  firstElementChild so whitespace-prefixed legacy content doesn't blow up
  on classList.
- details/edit.tsx: sync local isOpen state with the showContent toggle +
  the ToolsPanel reset so the rendered <details> reflects inspector
  changes immediately.
- group/placeholder.tsx: hide the placeholder for grid layouts too (not
  just flex) — initialization already treated them the same.
- button/button.css: word-break → overflow-wrap (modern equivalent;
  word-break: break-word was never in the official spec).
- separator/separator.css + group/group.css: currentColor → currentcolor
  per stylelint value-keyword-case; fix /*rtl:ignore*/ → /* rtl:ignore */
  spacing.
- spacer/spacer.css: drop the intra-rule empty lines that violated
  declaration-empty-line-before.
- button/upstream-state.json: refresh the edit.tsx divergence notes —
  they still described the window.prompt flow that the earlier review
  replaced with the LinkControl + Popover implementation.

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

♻️ Duplicate comments (1)
resources/js/visual-editor/blocks/details/edit.tsx (1)

158-158: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid rendering an empty name attribute on the <details> element.

The current fallback name={name || ''} renders name="" when unset, which can unintentionally group multiple unnamed <details> blocks together (browser treats them as a name-based exclusive group). The save component already uses name || undefined to omit the attribute; edit should match.

Proposed fix
-                name={name || ''}
+                name={name || undefined}
🤖 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/details/edit.tsx` at line 158, The edit
component currently sets the details element's name prop as name={name || ''},
which renders name="" when name is unset and can cause unintended grouping;
change the assignment in resources/js/visual-editor/blocks/details/edit.tsx so
the details element uses name={name || undefined} (or otherwise omits the
attribute) — update the place where the <details> element is rendered in the
edit component (look for the name prop reference) to match the save component's
behavior.
🤖 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.

Duplicate comments:
In `@resources/js/visual-editor/blocks/details/edit.tsx`:
- Line 158: The edit component currently sets the details element's name prop as
name={name || ''}, which renders name="" when name is unset and can cause
unintended grouping; change the assignment in
resources/js/visual-editor/blocks/details/edit.tsx so the details element uses
name={name || undefined} (or otherwise omits the attribute) — update the place
where the <details> element is rendered in the edit component (look for the name
prop reference) to match the save component's behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1013f987-6e97-4feb-9271-5f576ef56dce

📥 Commits

Reviewing files that changed from the base of the PR and between 52fcee4 and 2bc079b.

📒 Files selected for processing (11)
  • resources/js/visual-editor/blocks/button/button.css
  • resources/js/visual-editor/blocks/button/edit.tsx
  • resources/js/visual-editor/blocks/button/upstream-state.json
  • resources/js/visual-editor/blocks/buttons/transforms.ts
  • resources/js/visual-editor/blocks/columns/deprecated.tsx
  • resources/js/visual-editor/blocks/columns/transforms.ts
  • resources/js/visual-editor/blocks/details/edit.tsx
  • resources/js/visual-editor/blocks/group/group.css
  • resources/js/visual-editor/blocks/group/placeholder.tsx
  • resources/js/visual-editor/blocks/separator/separator.css
  • resources/js/visual-editor/blocks/spacer/spacer.css
💤 Files with no reviewable changes (1)
  • resources/js/visual-editor/blocks/spacer/spacer.css
✅ Files skipped from review due to trivial changes (2)
  • resources/js/visual-editor/blocks/button/upstream-state.json
  • resources/js/visual-editor/blocks/separator/separator.css
🚧 Files skipped from review as they are similar to previous changes (6)
  • resources/js/visual-editor/blocks/buttons/transforms.ts
  • resources/js/visual-editor/blocks/button/button.css
  • resources/js/visual-editor/blocks/columns/transforms.ts
  • resources/js/visual-editor/blocks/columns/deprecated.tsx
  • resources/js/visual-editor/blocks/group/placeholder.tsx
  • resources/js/visual-editor/blocks/group/group.css

@ViewFromTheBox ViewFromTheBox moved this from Backlog to In Review in ArtisanPack UI Overview May 28, 2026
@ViewFromTheBox
ViewFromTheBox marked this pull request as ready for review May 28, 2026 21:41
@ViewFromTheBox
ViewFromTheBox merged commit deb744f into release/1.0 May 28, 2026
1 check was pending
@ViewFromTheBox
ViewFromTheBox deleted the feature/411-layout-cluster branch May 28, 2026 21:41
@github-project-automation github-project-automation Bot moved this from In Review to Done in ArtisanPack UI Overview May 28, 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