Skip to content

E4 · Block re-enablement across Blade, React, Vue renderers - #396

Merged
ViewFromTheBox merged 2 commits into
release/1.0from
feature/381-e4-block-reenablement
Apr 27, 2026
Merged

ViewFromTheBox merged 2 commits into
release/1.0from
feature/381-e4-block-reenablement

Conversation

@ViewFromTheBox

@ViewFromTheBox ViewFromTheBox commented Apr 26, 2026 •

Copy link
Copy Markdown
Member

Description

Promote the entity-scoped block set off the V1 deny-list now that B1's expanded core-data shim and the C1–C5 backends can round-trip them, and add Blade / React / Vue render implementations for each so server- and client-rendered output stay in lockstep.

Closes: #381

Type of Change

  • New feature (adds new functionality)

Related Issue

Issue: #381

Motivation and Context

core-data started as a stub that disabled 17 blocks needing entity resolution. B1 expanded the shim, C1–C5 landed the backends. E4 is where the visible payoff lands — the editor's full block palette becomes usable.

Changes Made

Blocks promoted to the V1 allow-list (config/visual-editor.php + the JS-mirror in site-editor-app.tsx):

  • core/template-part
  • core/post-title, core/post-content, core/post-excerpt, core/post-date, core/post-author, core/post-featured-image
  • core/site-title, core/site-tagline, core/site-logo
  • core/navigation (+ core/navigation-link, core/navigation-submenu renderers)

Renderers added in all three packages. Each block reads the resolved post / site / author payload from a _resolved* attribute envelope a host-side resolver stamps onto the block tree (mirroring the template-part inliner pattern). When a value is missing the renderer emits a Gutenberg-shaped empty shell so the front-end markup matches the editor preview even before a resolver is wired up.

Shim selectors added so the newly-enabled Edit components don't crash against the empty-state core-data shim:

  • getCurrentTheme() returns a synthetic artisanpack-base theme record (matches config('artisanpack.visual-editor.global_styles.theme')) so the template-part enhanceTemplatePartVariations filter and placeholder code can read ?.stylesheet / ?.is_block_theme without throwing.
  • getThemeSupports() returns {}.
  • getPostType() returns null.
  • getNavigationFallbackId() is exposed as a public selector so block-library's unlock( useSelect( coreStore ) ) falls through to it (returns undefined to opt the navigation block into its uncontrolled-inner-blocks fallback).

Out of scope. Loop / feed widgets (core/query, core/latest-comments, core/archives, core/categories, core/tag-cloud) stay disabled — they need a real loop runtime + term/comment endpoints that the shim does not implement (V2 + V1.1+).

How Has This Been Tested?

Testing Environment:

  • Operating System: macOS 25.4
  • Browser (if applicable): Safari (manual editor testing)
  • PHP Version: 8.4.3
  • Project Version: release/1.0 HEAD

Tests Performed:

  1. Full Pest suite — 443 tests / 1,379 assertions, all passing.
  2. Full Vitest suite (renderer packages + shim) — 338 tests passing across 24 files.
  3. Manual browser test in the dev app's site editor: insert each new block type, confirm no crashes, save + reload to confirm round-trip.
  4. Three CodeRabbit review passes against release/1.0 — final pass returned no findings (resolved 4 XSS-via-rel concatenation findings + 1 inverted-logic finding on the navigation is-responsive class along the way).

Tests Added

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

Test details:

  • Blade Pest feature test (PostAndSiteContextRenderingTest.php) covering all 11 newly-enabled blocks plus the navigation menu / submenu rendering matrix and URL-sanitisation safeguards on _resolvedPermalink.
  • Vitest feature tests for the React and Vue renderers (PostAndSiteContextRendering.test.{tsx,ts}) — 18 tests each, mirroring the Blade matrix.
  • Registry test additions in both JS renderer packages assert all 12 newly-registered block names (post-context × 6, site-context × 3, navigation × 3) are present after registerCoreBlocks().
  • EnabledBlockNamesTest snapshot updated to reflect the new V1 allow-list ordering.
  • Three new shim tests cover getCurrentTheme(), getPostType(), getThemeSupports(), and getNavigationFallbackId().

Accessibility Tests Run

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

Details:

The renderer output preserves Gutenberg core's wrapper semantics (correct heading levels for post/site title via level clamp, <time datetime> on post-date, <figure> on featured image, <nav aria-label> on navigation, <ul> menu containers for navigation children). No new interactive surfaces are introduced — all blocks render to static HTML. Tests assert aria-label propagation on the navigation block.

Documentation

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

Documentation details:

Each new renderer file carries a top-of-file docblock explaining the _resolved* attribute envelope + parity goal with the other renderers. Inline comments in the shim explain why each new selector exists and what it returns. README / wiki updates can land alongside the resolver work that fills in the _resolved* attribute envelope (filed separately).

Screenshots

Manual browser testing was performed in the site editor; no screenshot panel needed since the visible output is "blocks no longer crash on insert" and the rendered markup matches Gutenberg's wrapper semantics.

Pre-Submission Checklist

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

Follow-up issues opened during testing

Two pre-existing issues surfaced while exercising the new blocks. Filed separately so this PR can land without bundling shim or shell work:

Summary by CodeRabbit

  • New Features

    • Enabled rendering support for core post-context blocks (title, content, excerpt, date, author, featured image), site-context blocks (title, tagline, logo), and navigation (links, submenus) across Blade, React, and Vue renderers; updated editor config to re-enable these entity-scoped blocks.
  • Tests

    • Added end-to-end and unit tests validating HTML structure, attributes, link target/rel behavior, security filtering of unsafe URLs, and registry registrations for post/site/navigation blocks.

… renderers

Promote the entity-scoped block set off the V1 deny-list now that B1's
expanded core-data shim and the C1–C5 backends can round-trip them, and
add Blade / React / Vue render implementations for each so server- and
client-rendered output stay in lockstep.

Newly exposed blocks (config + JS-mirror):

- core/template-part
- core/post-title, core/post-content, core/post-excerpt, core/post-date,
  core/post-author, core/post-featured-image
- core/site-title, core/site-tagline, core/site-logo
- core/navigation (+ core/navigation-link, core/navigation-submenu
  renderers)

Renderers read the resolved post / site / author payload from a
`_resolved*` attribute envelope a host-side resolver stamps onto the
block tree, mirroring the template-part inliner pattern. When a value
is missing the renderer emits a Gutenberg-shaped empty shell so the
front-end markup matches the editor preview even before a resolver is
wired up.

Shim stubs added so the newly-enabled edit components don't crash
against the empty-state core-data shim:

- `getCurrentTheme()` returns a synthetic `artisanpack-base` theme
  record (matching `config('artisanpack.visual-editor.global_styles.
  theme')`) so the template-part `enhanceTemplatePartVariations`
  filter and placeholder code can read `?.stylesheet` /
  `?.is_block_theme` without throwing.
- `getThemeSupports()` returns `{}`.
- `getPostType()` returns null.
- `getNavigationFallbackId()` is exposed as a public selector so
  block-library's `unlock( useSelect( coreStore ) )` falls through to
  it (returns `undefined` to opt the navigation block into its
  uncontrolled-inner-blocks fallback).

Scope notes:

- Loop / feed widgets (core/query, core/latest-comments, etc.) stay
  disabled — they need a real loop runtime + term/comment endpoints
  that the shim does not implement (V2 + V1.1+).
- Inspector sidebar registry-isolation bug and core-data shim
  auto-fetch gap surfaced during manual testing but predate E4 and are
  filed as #394 and #395 respectively.

Closes #381
@coderabbitai

coderabbitai Bot commented Apr 26, 2026 •

Copy link
Copy Markdown

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: c652958b-372d-46cb-9485-5c375e5bd0da

📥 Commits

Reviewing files that changed from the base of the PR and between 5f02712 and 539a195.

📒 Files selected for processing (1)
  • config/visual-editor.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • config/visual-editor.php

📝 Walkthrough

Walkthrough

Re-enables entity-scoped core blocks and implements renderers for navigation, post-context, and site-context blocks across Blade, React, and Vue. Updates the core-data shim and visual-editor configuration. Adds per-renderer tests and registry entries; expands unit tests and snapshots to reflect the newly enabled blocks.

Changes

Cohort / File(s) Summary
Config & Core-data shim
config/visual-editor.php, resources/js/visual-editor/vendor/core-data-shim.ts, resources/js/visual-editor/site-editor/site-editor-app.tsx
Moves entity-scoped template/post/site/navigation blocks into the V1 enabled list; reduces disabled_blocks to loop/feed/taxonomy widgets. Adds selector stubs (getCurrentTheme, getThemeSupports, getPostType, getNavigationFallbackId) and updates comments/docs.
Blade renderer templates
packages/visual-editor-renderer-blade/resources/views/blocks/core/navigation.blade.php, .../navigation-link.blade.php, .../navigation-submenu.blade.php, .../post-title.blade.php, .../post-content.blade.php, .../post-excerpt.blade.php, .../post-date.blade.php, .../post-author.blade.php, .../post-featured-image.blade.php, .../site-title.blade.php, .../site-tagline.blade.php, .../site-logo.blade.php
Adds Blade view templates for navigation, post-context, and site-context blocks with URL sanitization, class assembly, conditional link/image rendering, and safe/unsafe HTML handling where appropriate.
Blade tests
packages/visual-editor-renderer-blade/tests/Feature/PostAndSiteContextRenderingTest.php
Adds feature tests asserting rendered HTML structure, link/rel/target behavior, image sizing/attributes, resolved content output, and navigation hierarchy/security.
React renderers
packages/visual-editor-renderer-react/src/blocks/core/navigation.tsx, .../postContext.tsx, .../siteContext.tsx, .../registerCoreBlocks.ts
Adds React renderer components for navigation, post-context, and site-context blocks and registers them in CORE_BLOCKS. Includes shared link-props helper, URL sanitization, class computation, and conditional rendering.
React tests
packages/visual-editor-renderer-react/tests/PostAndSiteContextRendering.test.tsx, packages/visual-editor-renderer-react/tests/registry.test.ts
Adds integration tests validating normalized HTML for post/site/navigation blocks and a registry test confirming new block registrations.
Vue renderers
packages/visual-editor-renderer-vue/src/blocks/core/navigation.ts, .../postContext.ts, .../siteContext.ts, .../registerCoreBlocks.ts
Adds Vue defineComponent renderers for navigation, post-context, and site-context blocks, with safeUrl sanitization, computed classes, shared link props, and registry extensions.
Vue tests
packages/visual-editor-renderer-vue/tests/PostAndSiteContextRendering.test.ts, packages/visual-editor-renderer-vue/tests/registry.test.ts
Adds Vitest Vue tests asserting mounted HTML output, security (blocking javascript:), navigation structure/ARIA, and registry completeness.
Unit tests & snapshots
resources/js/visual-editor/vendor/__tests__/core-data-shim.test.tsx, tests/Unit/VisualEditor/EnabledBlockNamesTest.php
Extends core-data-shim unit tests for new selector stubs and updates enabled-blocks snapshot to include newly enabled entity-scoped core blocks.

Sequence Diagram(s)

(No sequence diagrams generated.)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 Hop, hop—blocks awaken, freed from night,

Blade, React, and Vue now render right.
Links are safe and images glow,
Tests sing green and menus flow.
A carrot-cheer from this rabbit friend—hoppity-hooray, renderers mend!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'E4 · Block re-enablement across Blade, React, Vue renderers' clearly and specifically summarizes the main change: re-enabling blocks across multiple renderer implementations.
Linked Issues check ✅ Passed The PR implements all in-scope requirements from #381: core blocks re-enabled in registries, render implementations added for Blade/React/Vue, per-block tests included, and consistent output across renderers verified.
Out of Scope Changes check ✅ Passed All changes directly support block re-enablement and renderer implementation. Configuration updates, renderer templates, and comprehensive test coverage align with stated objectives.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/381-e4-block-reenablement

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

@ViewFromTheBox ViewFromTheBox moved this from Backlog to In Review in ArtisanPack UI Overview Apr 26, 2026
@ViewFromTheBox ViewFromTheBox linked an issue Apr 26, 2026 that may be closed by this pull request
7 tasks
@ViewFromTheBox
ViewFromTheBox marked this pull request as ready for review April 26, 2026 23:26

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

Caution

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

⚠️ Outside diff range comments (1)
packages/visual-editor-renderer-blade/tests/Feature/PostAndSiteContextRenderingTest.php (1)

226-271: ⚠️ Potential issue | 🟡 Minor

Add Blade parity test for javascript: navigation URLs.

Navigation sanitization is asserted in React/Vue, but Blade currently lacks the negative-path assertion. Add it to keep renderer parity and catch regressions.

Suggested test addition
 it( 'forces noopener when a navigation link opens in a new tab', function () {
@@
 	expect( $rendered )->toContain( 'rel="noopener noreferrer"' );
 } );
+
+it( 'drops javascript: navigation link URLs', function () {
+	$tree = [
+		blockNode( 'core/navigation', [], [
+			blockNode( 'core/navigation-link', [
+				'label' => 'Bad',
+				'url'   => 'javascript:alert(1)',
+			], [], 'nl-1' ),
+		] ),
+	];
+
+	$rendered = $this->stripGlobalStyles( renderTree( $tree ) );
+
+	expect( $rendered )->not()->toContain( 'javascript:' );
+	expect( $rendered )->not()->toContain( 'href=' );
+} );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/visual-editor-renderer-blade/tests/Feature/PostAndSiteContextRenderingTest.php`
around lines 226 - 271, Add a Blade parity test to
PostAndSiteContextRenderingTest that asserts navigation links with "javascript:"
URLs are sanitized (i.e., not rendered as clickable hrefs) similar to React/Vue
tests: create a new it( 'does not render javascript: navigation URLs', ... )
case using blockNode to build a core/navigation containing a
core/navigation-link with 'url' => 'javascript:alert(1)', render it via
renderTree and stripGlobalStyles, and assert the rendered output does NOT
contain 'href="javascript:alert(1)"' and instead either omits the href or
escapes it appropriately; locate where other navigation tests live (the existing
it( 'renders a navigation block with menu items' ) and it( 'forces noopener...'
) cases) and add the new test nearby using the same helpers (blockNode,
renderTree, stripGlobalStyles) for parity.
🧹 Nitpick comments (3)
packages/visual-editor-renderer-blade/resources/views/blocks/core/site-title.blade.php (1)

24-24: Consider omitting redundant target="_self" on site-title links.

Line 24 always prints target, but _self is default browser behavior. Emitting target only for _blank keeps markup tighter and reduces renderer drift risk.

♻️ Suggested adjustment
-<{{ $tag }} class="{{ implode( ' ', array_map( 'trim', $classes ) ) }}">@if ( $isLink && '' !== $siteUrl )<a href="{{ $siteUrl }}" target="{{ $linkTarget }}" rel="{{ '_blank' === $linkTarget ? 'noopener noreferrer' : 'home' }}">{{ $title }}</a>@else{{ $title }}@endif</{{ $tag }}>
+<{{ $tag }} class="{{ implode( ' ', array_map( 'trim', $classes ) ) }}">
+@if ( $isLink && '' !== $siteUrl )
+	<a href="{{ $siteUrl }}"@if ( '_blank' === $linkTarget ) target="_blank" rel="noopener noreferrer"@else rel="home"@endif>{{ $title }}</a>
+@else
+	{{ $title }}
+@endif
+</{{ $tag }}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/visual-editor-renderer-blade/resources/views/blocks/core/site-title.blade.php`
at line 24, The template always emits target="{{ $linkTarget }}" for the
site-title link even when $linkTarget is "_self"; change the markup in the <{{
$tag }}> block that builds the anchor (the code using $isLink, $siteUrl,
$linkTarget, $title) to only render a target attribute when $linkTarget ===
'_blank' and only render rel="noopener noreferrer" in that same branch
(otherwise omit target and rel entirely) so default browser behavior (_self)
isn't redundantly output.
packages/visual-editor-renderer-vue/tests/PostAndSiteContextRendering.test.ts (1)

2-2: Initialize registry in beforeEach to remove first-test state coupling.

Line 21 only re-initializes after a test, so the first case depends on Line 4 side effects. Prefer explicit per-test setup.

Proposed test-lifecycle adjustment
-import { afterEach, describe, expect, it } from 'vitest';
+import { afterEach, beforeEach, describe, expect, it } from 'vitest';
@@
-afterEach(() => {
+beforeEach(() => {
     resetBlockRegistry();
     registerCoreBlocks();
 });
+
+afterEach(() => {
+    resetBlockRegistry();
+});

Also applies to: 21-24

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/visual-editor-renderer-vue/tests/PostAndSiteContextRendering.test.ts`
at line 2, The registry used in PostAndSiteContextRendering.test.ts is only
being reset in afterEach causing the first test to depend on module-level
initialization; add an explicit beforeEach that initializes or reassigns the
shared registry (the same variable currently reset in afterEach) so each test
starts with a fresh registry instance, and keep or retain the existing afterEach
cleanup if needed; update references in the describe block and tests that
consume the registry to use this per-test initialization.
packages/visual-editor-renderer-react/tests/PostAndSiteContextRendering.test.tsx (1)

2-2: Prefer explicit beforeEach registration for deterministic setup.

Line 18 initializes state only after tests. Seed registry in beforeEach so the first test doesn’t rely on import-time state.

Proposed lifecycle change
-import { afterEach, describe, expect, it } from 'vitest';
+import { afterEach, beforeEach, describe, expect, it } from 'vitest';
@@
-afterEach(() => {
+beforeEach(() => {
     resetBlockRegistry();
     registerCoreBlocks();
 });
+
+afterEach(() => {
+    resetBlockRegistry();
+});

Also applies to: 18-21

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/visual-editor-renderer-react/tests/PostAndSiteContextRendering.test.tsx`
at line 2, Tests rely on import-time state seeding so make setup deterministic
by moving registry seeding into an explicit beforeEach: add a beforeEach hook
that calls the existing registry seed function (the same code currently executed
at import/after tests) to initialize state before every test, keep afterEach for
cleanup/unregistering, and update any references to the seed call (e.g.,
seedRegistry or registerTestData) so the first test no longer depends on
import-time side effects; ensure beforeEach runs before tests by placing it
alongside the existing afterEach in PostAndSiteContextRendering.test.tsx.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@config/visual-editor.php`:
- Around line 87-119: Update the stale explanatory paragraph earlier in this
file so it describes the E4 reality: mention that blocks like
'core/template-part', 'core/post-*', and 'core/site-*' are now promoted to the
allowed list (as shown in the allow-list array) and that 'core/navigation'
remains enabled, while deferred items such as 'core/query' remain in the
'disabled_blocks' array pending runtime/endpoint support; make the prose
consistent with the comments immediately above the 'disabled_blocks' array and
note that the JS mirror in site-editor-app.tsx must be kept in sync.

In
`@packages/visual-editor-renderer-blade/resources/views/blocks/core/post-title.blade.php`:
- Line 42: The post title is being rendered as raw HTML using {!! $title !!},
creating a stored-XSS risk if upstream _resolvedTitle is unsanitized; update the
template to output an escaped title instead (use Blade's escaped output for the
$title variable) while keeping existing link logic ($isLink, $permalink,
$linkAttrs, $tag, $classes) intact so markup and attributes remain unchanged.

In `@packages/visual-editor-renderer-react/src/blocks/core/postContext.tsx`:
- Line 66: Several places in postContext.tsx render raw HTML into the DOM (e.g.,
the anchor using linkProps and title: the dangerouslySetInnerHTML={{ __html:
title }} sink) without sanitization; replace direct injection by importing a
sanitizer (DOMPurify or equivalent) and run every `_resolved*`-derived value
through it before passing to dangerouslySetInnerHTML (e.g., sanitize title, plus
the other `_resolved*` values used at the other sinks on this file) so the
rendered payload is DOMPurify.sanitize(value) (or your project's sanitizer API)
and update all occurrences in postContext.tsx to use the sanitized string.

In `@packages/visual-editor-renderer-vue/src/blocks/core/postContext.ts`:
- Around line 66-70: The renderer currently injects _resolved* HTML fields into
the DOM via innerHTML in postContext.ts (see the h(..., { innerHTML: ... })
usages) while attrString() only coerces types and does not escape HTML; either
ensure the upstream host-side resolver that produces these _resolved* properties
performs robust HTML sanitization (e.g., DOMPurify or equivalent) before
stamping them onto the block tree, or perform escaping/sanitization at this
boundary inside postContext.ts before assigning to innerHTML (replace or
sanitize the _resolved* values returned by the resolver); update or add tests
asserting that _resolved* properties passed to render functions are sanitized
and reference the attrString(), the renderer's innerHTML assignments, and the
host-side resolver API when making the change.

---

Outside diff comments:
In
`@packages/visual-editor-renderer-blade/tests/Feature/PostAndSiteContextRenderingTest.php`:
- Around line 226-271: Add a Blade parity test to
PostAndSiteContextRenderingTest that asserts navigation links with "javascript:"
URLs are sanitized (i.e., not rendered as clickable hrefs) similar to React/Vue
tests: create a new it( 'does not render javascript: navigation URLs', ... )
case using blockNode to build a core/navigation containing a
core/navigation-link with 'url' => 'javascript:alert(1)', render it via
renderTree and stripGlobalStyles, and assert the rendered output does NOT
contain 'href="javascript:alert(1)"' and instead either omits the href or
escapes it appropriately; locate where other navigation tests live (the existing
it( 'renders a navigation block with menu items' ) and it( 'forces noopener...'
) cases) and add the new test nearby using the same helpers (blockNode,
renderTree, stripGlobalStyles) for parity.

---

Nitpick comments:
In
`@packages/visual-editor-renderer-blade/resources/views/blocks/core/site-title.blade.php`:
- Line 24: The template always emits target="{{ $linkTarget }}" for the
site-title link even when $linkTarget is "_self"; change the markup in the <{{
$tag }}> block that builds the anchor (the code using $isLink, $siteUrl,
$linkTarget, $title) to only render a target attribute when $linkTarget ===
'_blank' and only render rel="noopener noreferrer" in that same branch
(otherwise omit target and rel entirely) so default browser behavior (_self)
isn't redundantly output.

In
`@packages/visual-editor-renderer-react/tests/PostAndSiteContextRendering.test.tsx`:
- Line 2: Tests rely on import-time state seeding so make setup deterministic by
moving registry seeding into an explicit beforeEach: add a beforeEach hook that
calls the existing registry seed function (the same code currently executed at
import/after tests) to initialize state before every test, keep afterEach for
cleanup/unregistering, and update any references to the seed call (e.g.,
seedRegistry or registerTestData) so the first test no longer depends on
import-time side effects; ensure beforeEach runs before tests by placing it
alongside the existing afterEach in PostAndSiteContextRendering.test.tsx.

In
`@packages/visual-editor-renderer-vue/tests/PostAndSiteContextRendering.test.ts`:
- Line 2: The registry used in PostAndSiteContextRendering.test.ts is only being
reset in afterEach causing the first test to depend on module-level
initialization; add an explicit beforeEach that initializes or reassigns the
shared registry (the same variable currently reset in afterEach) so each test
starts with a fresh registry instance, and keep or retain the existing afterEach
cleanup if needed; update references in the describe block and tests that
consume the registry to use this per-test initialization.
🪄 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: 4b4038a4-b43a-47f7-a0dc-9a0f72629536

📥 Commits

Reviewing files that changed from the base of the PR and between 725e4b6 and 5f02712.

📒 Files selected for processing (30)
  • config/visual-editor.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/core/navigation-link.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/core/navigation-submenu.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/core/navigation.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/core/post-author.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/core/post-content.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/core/post-date.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/core/post-excerpt.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/core/post-featured-image.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/core/post-title.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/core/site-logo.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/core/site-tagline.blade.php
  • packages/visual-editor-renderer-blade/resources/views/blocks/core/site-title.blade.php
  • packages/visual-editor-renderer-blade/tests/Feature/PostAndSiteContextRenderingTest.php
  • packages/visual-editor-renderer-react/src/blocks/core/navigation.tsx
  • packages/visual-editor-renderer-react/src/blocks/core/postContext.tsx
  • packages/visual-editor-renderer-react/src/blocks/core/siteContext.tsx
  • packages/visual-editor-renderer-react/src/blocks/registerCoreBlocks.ts
  • packages/visual-editor-renderer-react/tests/PostAndSiteContextRendering.test.tsx
  • packages/visual-editor-renderer-react/tests/registry.test.ts
  • packages/visual-editor-renderer-vue/src/blocks/core/navigation.ts
  • packages/visual-editor-renderer-vue/src/blocks/core/postContext.ts
  • packages/visual-editor-renderer-vue/src/blocks/core/siteContext.ts
  • packages/visual-editor-renderer-vue/src/blocks/registerCoreBlocks.ts
  • packages/visual-editor-renderer-vue/tests/PostAndSiteContextRendering.test.ts
  • packages/visual-editor-renderer-vue/tests/registry.test.ts
  • resources/js/visual-editor/site-editor/site-editor-app.tsx
  • resources/js/visual-editor/vendor/__tests__/core-data-shim.test.tsx
  • resources/js/visual-editor/vendor/core-data-shim.ts
  • tests/Unit/VisualEditor/EnabledBlockNamesTest.php

Comment thread config/visual-editor.php
}
}
@endphp
<{{ $tag }} class="{{ implode( ' ', array_map( 'trim', $classes ) ) }}">@if ( $isLink && '' !== $permalink )<a{!! $linkAttrs !!}>{!! $title !!}</a>@else{!! $title !!}@endif</{{ $tag }}>

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 | 🟠 Major

Escape post title output instead of rendering raw HTML.

Line 42 outputs $title with {!! $title !!}. That creates a stored-XSS path if _resolvedTitle is ever unsanitized upstream. Use escaped output for title text.

🔧 Proposed fix
-<{{ $tag }} class="{{ implode( ' ', array_map( 'trim', $classes ) ) }}">@if ( $isLink && '' !== $permalink )<a{!! $linkAttrs !!}>{!! $title !!}</a>@else{!! $title !!}@endif</{{ $tag }}>
+<{{ $tag }} class="{{ implode( ' ', array_map( 'trim', $classes ) ) }}">@if ( $isLink && '' !== $permalink )<a{!! $linkAttrs !!}>{{ $title }}</a>@else{{ $title }}@endif</{{ $tag }}>
📝 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.

Suggested change
<{{ $tag }} class="{{ implode( ' ', array_map( 'trim', $classes ) ) }}">@if ( $isLink && '' !== $permalink )<a{!! $linkAttrs !!}>{!! $title !!}</a>@else{!! $title !!}@endif</{{ $tag }}>
<{{ $tag }} class="{{ implode( ' ', array_map( 'trim', $classes ) ) }}">@if ( $isLink && '' !== $permalink )<a{!! $linkAttrs !!}>{{ $title }}</a>@else{{ $title }}@endif</{{ $tag }}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/visual-editor-renderer-blade/resources/views/blocks/core/post-title.blade.php`
at line 42, The post title is being rendered as raw HTML using {!! $title !!},
creating a stored-XSS risk if upstream _resolvedTitle is unsanitized; update the
template to output an escaped title instead (use Blade's escaped output for the
$title variable) while keeping existing link logic ($isLink, $permalink,
$linkAttrs, $tag, $classes) intact so markup and attributes remain unchanged.


return (
<Tag className={classes}>
<a {...linkProps} dangerouslySetInnerHTML={{ __html: title }} />

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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Where resolved HTML payloads are produced/assigned
rg -n --type=ts --type=tsx --type=php '_resolved(Content|Excerpt|Title|AuthorBio)'

# Existing sanitizer usage and dangerous sinks
rg -n --type=ts --type=tsx --type=php 'dangerouslySetInnerHTML|DOMPurify|sanitize|safeHtml|wp_kses|kses'

Repository: ArtisanPack-UI/visual-editor

Length of output: 100


🏁 Script executed:

# Find and read the target file
fd -t f "postContext.tsx" 2>/dev/null | head -5

Repository: ArtisanPack-UI/visual-editor

Length of output: 142


🏁 Script executed:

# Get the file size first
wc -l packages/visual-editor-renderer-react/src/blocks/core/postContext.tsx 2>/dev/null || echo "File not found"

Repository: ArtisanPack-UI/visual-editor

Length of output: 146


🏁 Script executed:

# Search for _resolved patterns without type restrictions
rg -n '_resolved(Content|Excerpt|Title|AuthorBio)' --max-count=50 2>/dev/null || true

Repository: ArtisanPack-UI/visual-editor

Length of output: 4259


🏁 Script executed:

# Search for dangerouslySetInnerHTML and sanitization patterns
rg -n 'dangerouslySetInnerHTML|DOMPurify|sanitize|safeHtml' --max-count=50 2>/dev/null || true

Repository: ArtisanPack-UI/visual-editor

Length of output: 8817


🏁 Script executed:

cat -n packages/visual-editor-renderer-react/src/blocks/core/postContext.tsx

Repository: ArtisanPack-UI/visual-editor

Length of output: 11542


🏁 Script executed:

cat packages/visual-editor-renderer-react/package.json | grep -A 20 '"dependencies"'

Repository: ArtisanPack-UI/visual-editor

Length of output: 54


🏁 Script executed:

cat packages/visual-editor-renderer-react/package.json

Repository: ArtisanPack-UI/visual-editor

Length of output: 890


🏁 Script executed:

cat packages/visual-editor-renderer-react/src/support/attributes.ts

Repository: ArtisanPack-UI/visual-editor

Length of output: 2690


🏁 Script executed:

fd -type f "Resolver" --max-depth 5 | head -10

Repository: ArtisanPack-UI/visual-editor

Length of output: 243


🏁 Script executed:

rg -l "resolvedTitle|resolvedContent" --type=ts --type=tsx --max-count=20 2>/dev/null || true

Repository: ArtisanPack-UI/visual-editor

Length of output: 54


🏁 Script executed:

# Check the Blade templates to see if they sanitize
cat packages/visual-editor-renderer-blade/resources/views/blocks/core/post-title.blade.php

Repository: ArtisanPack-UI/visual-editor

Length of output: 1998


🏁 Script executed:

# Check the Vue templates to see how they handle it
rg -A 5 "_resolvedTitle|_resolvedContent" packages/visual-editor-renderer-vue/src/blocks/core/postContext.ts | head -40

Repository: ArtisanPack-UI/visual-editor

Length of output: 556


🏁 Script executed:

# Find where _resolved* attributes are set
rg -n "_resolvedTitle\s*=" --max-count=20 2>/dev/null || true

Repository: ArtisanPack-UI/visual-editor

Length of output: 54


🏁 Script executed:

# Look for where these resolved attributes are created
rg -B 3 -A 3 "attributes\._resolved" --max-count=30 2>/dev/null | head -50

Repository: ArtisanPack-UI/visual-editor

Length of output: 5157


🏁 Script executed:

# Check for any sanitization in the main codebase
rg -n "wp_kses|sanitize.*html|DOMPurify" --max-count=20 2>/dev/null || true

Repository: ArtisanPack-UI/visual-editor

Length of output: 54


🏁 Script executed:

# Search for resolver or data processing that might sanitize
fd -name "*esolver*" 2>/dev/null | head -10

Repository: ArtisanPack-UI/visual-editor

Length of output: 54


Add HTML sanitization for all dangerouslySetInnerHTML sinks using _resolved* values.

Lines 66, 71, 86, 123, and 205 inject HTML directly from _resolved* attributes without sanitization. These values are injected at the renderer boundary after attributes are populated upstream. Without explicit sanitization, any unsanitized content flowing through the resolver becomes an XSS vector.

Add DOMPurify (or equivalent) and sanitize these payloads before render:

Sanitization approach
+import DOMPurify from 'dompurify';
+
+function sanitizeHtml(html: string): string {
+    return DOMPurify.sanitize(html, { ALLOWED_TAGS: [] });
+}

-const title = attrString(attributes._resolvedTitle);
+const title = sanitizeHtml(attrString(attributes._resolvedTitle));

-const content = attrString(attributes._resolvedContent);
+const content = sanitizeHtml(attrString(attributes._resolvedContent));

-const excerpt = attrString(attributes._resolvedExcerpt);
+const excerpt = sanitizeHtml(attrString(attributes._resolvedExcerpt));

-const bio = attrString(attributes._resolvedAuthorBio);
+const bio = sanitizeHtml(attrString(attributes._resolvedAuthorBio));

Alternatively, document and enforce a strict trusted-HTML contract at the resolver boundary (e.g., resolver must sanitize output before stamping _resolved* attributes).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/visual-editor-renderer-react/src/blocks/core/postContext.tsx` at
line 66, Several places in postContext.tsx render raw HTML into the DOM (e.g.,
the anchor using linkProps and title: the dangerouslySetInnerHTML={{ __html:
title }} sink) without sanitization; replace direct injection by importing a
sanitizer (DOMPurify or equivalent) and run every `_resolved*`-derived value
through it before passing to dangerouslySetInnerHTML (e.g., sanitize title, plus
the other `_resolved*` values used at the other sinks on this file) so the
rendered payload is DOMPurify.sanitize(value) (or your project's sanitizer API)
and update all occurrences in postContext.tsx to use the sanitized string.

Comment on lines +66 to +70
return h(tag, { class: classes }, [h('a', { ...linkProps, innerHTML: title })]);
}

return h(tag, { class: classes, innerHTML: title });
};

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Locate where resolved payload fields are produced.
rg -n --type=ts --type=tsx --type=js --type=jsx --type=php \
  "_resolvedTitle|_resolvedContent|_resolvedExcerpt|_resolvedAuthorBio|_resolvedDateFormatted|_resolvedPermalink"

# 2) Inspect sanitization/escaping patterns in those producer areas.
rg -n -C3 --type=ts --type=tsx --type=js --type=jsx --type=php \
  "sanitize|saniti[sz]e|escape|esc_html|esc_attr|wp_kses|DOMPurify|safeHtml|xss"

# Expected: `_resolved*` HTML-producing paths sanitize/escape once before renderer consumption.

Repository: ArtisanPack-UI/visual-editor

Length of output: 100


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# First, check if the file exists and get its size
echo "=== Checking postContext.ts file ==="
find . -name "postContext.ts" -type f

# Check available file types for rg
echo -e "\n=== Searching for _resolved* patterns (correct file types) ==="
rg -n "_resolvedTitle|_resolvedContent|_resolvedExcerpt|_resolvedAuthorBio|_resolvedDateFormatted|_resolvedPermalink" \
  --type ts --type js \
  || echo "No matches found"

# Look for sanitization patterns across the codebase
echo -e "\n=== Searching for sanitization patterns ==="
rg -n "sanitize|escape|DOMPurify|safeHtml|xss" \
  --type ts --type js --type vue \
  | head -30

Repository: ArtisanPack-UI/visual-editor

Length of output: 5851


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the specific file to understand the context
echo "=== Reading postContext.ts ==="
file_path=$(find . -name "postContext.ts" -type f)
if [ -n "$file_path" ]; then
  wc -l "$file_path"
  echo -e "\n=== Full content of postContext.ts ==="
  cat -n "$file_path"
else
  echo "File not found"
fi

Repository: ArtisanPack-UI/visual-editor

Length of output: 14130


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find and read the attributes support file
echo "=== Finding attributes support file ==="
find . -path "*/support/attributes*" -type f

# Read the attributes file to see attrString implementation
echo -e "\n=== Reading attributes file ==="
file_path=$(find . -path "*/support/attributes*" -type f | head -1)
if [ -n "$file_path" ]; then
  echo "File: $file_path"
  wc -l "$file_path"
  echo ""
  cat -n "$file_path"
else
  echo "Attributes file not found"
fi

Repository: ArtisanPack-UI/visual-editor

Length of output: 3757


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check the urlSanitizer implementation to understand sanitization approach
echo "=== Finding urlSanitizer file ==="
find . -path "*/support/urlSanitizer*" -type f

# Read the urlSanitizer file
echo -e "\n=== Reading urlSanitizer file ==="
file_path=$(find . -path "*/support/urlSanitizer*" -type f | head -1)
if [ -n "$file_path" ]; then
  echo "File: $file_path"
  wc -l "$file_path"
  echo ""
  cat -n "$file_path"
else
  echo "urlSanitizer file not found"
fi

Repository: ArtisanPack-UI/visual-editor

Length of output: 1588


Verify that _resolved* HTML fields are sanitized at the resolver boundary.

Lines 66, 69, 90, 131, and 252 render _resolved* HTML values via innerHTML bindings. The attrString() function applies no HTML escaping—it only performs type coercion. The security responsibility is delegated to the upstream "host-side resolver" that stamps these values onto the block tree. Confirm that this resolver applies HTML sanitization (e.g., wp_kses, DOMPurify equivalent) before these attributes reach the renderer, or implement HTML escaping here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/visual-editor-renderer-vue/src/blocks/core/postContext.ts` around
lines 66 - 70, The renderer currently injects _resolved* HTML fields into the
DOM via innerHTML in postContext.ts (see the h(..., { innerHTML: ... }) usages)
while attrString() only coerces types and does not escape HTML; either ensure
the upstream host-side resolver that produces these _resolved* properties
performs robust HTML sanitization (e.g., DOMPurify or equivalent) before
stamping them onto the block tree, or perform escaping/sanitization at this
boundary inside postContext.ts before assigning to innerHTML (replace or
sanitize the _resolved* values returned by the resolver); update or add tests
asserting that _resolved* properties passed to render functions are sanitized
and reference the attrString(), the renderer's innerHTML assignments, and the
host-side resolver API when making the change.

The block-registry filter docblock above `enabled_blocks` still
described the pre-E4 state where every entity-scoped block was
disabled. Update it to reflect the V1 reality (E4 promoted
`core/template-part`, `core/post-*`, `core/site-*`, and
`core/navigation`) and call out which blocks remain deferred to V2 /
V1.1+, plus a cross-reference to the JS-mirror in `site-editor-app.tsx`
that needs to stay in sync with the deny-list.
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.

E4 · Block re-enablement across Blade, React, Vue renderers

1 participant