Skip to content

fix: render fidelity for code-block-pro font and core/list values - #70

Merged
zackkatz merged 7 commits into
developfrom
fix/block-render-fidelity
Aug 10, 2026
Merged

fix: render fidelity for code-block-pro font and core/list values#70
zackkatz merged 7 commits into
developfrom
fix/block-render-fidelity

Conversation

@zackkatz

@zackkatz zackkatz commented Jul 24, 2026

Copy link
Copy Markdown
Member

Fixes two cases where a block was saved with content the block's own render ignores, so it looked correct in the tool response but rendered wrong on the front end.

Font — code-block-pro rendered serif

src/enrichers.ts baked font-family:<name> with no generic family. A custom CBP font name (e.g. Code-Pro-JetBrains-Mono) is not a loaded webfont, so with no monospace fallback browsers fell back to the default serif. Added ensureMonospaceFallback(): when the value has no generic family keyword it appends ,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace (what the CBP editor bakes), applied before escapeAttr. Idempotent — a word-boundary monospace match also catches ui-monospace and any value already ending in monospace, so re-runs never double-append. Only the build-from-scratch branch is touched.

List — core/list saved an empty <ul>

Inserting a core/list with its <li> items in the deprecated values attribute and an empty <ul> wrapper (no core/list-item children) saved a block that renders an empty list on modern WordPress. Added includes/block-normalizers/class-core-list-normalizer.php, modeled on the core/image normalizer and registered on the non-bypassable gk/block-mcp/block/normalize write chokepoint. It bakes the values items into the wrapper (reconciling <ul>/<ol> from the ordered attribute, preserving nested sublists and inline links) and keeps the block a leaf (sets innerContent). Three guards make it idempotent and never false-repair: it skips when innerBlocks is non-empty, when values is empty, or when the wrapper already contains an <li>. The detect signature (populated values in the comment attrs + empty wrapper + no <li>) is one no valid or deprecated Gutenberg serialization can produce.

Why different layers

The MCP generates the CBP wrapper in TS (fix at the source there), but passes list markup through verbatim to PHP, where the write-path normalizer is the authoritative, non-bypassable place to repair it.

Tests

TDD (RED proven for both). Full gate green: TS npm test 891/891; PHP composer test 1455 + 28 + 11 + 5 all OK; composer lint 0/0; composer analyze (PHPStan L5) [OK]. Adversarially reviewed.

Note

@since TODO on the new PHP class — 2.2.0 is already released and the next version isn't set; resolve to the shipped version at release.

https://claude.ai/code/session_017AMC5bVpEM9G3WtXbXtmwj

💾 Build file (5808211).

Summary by CodeRabbit

  • Bug Fixes

    • Code blocks now retain a monospace fallback when no fallback font is provided.
    • Font changes now update displayed code and synchronize styling consistently.
    • Repaired malformed lists so saved items display correctly, including ordered lists and nested content.
    • Preserved list classes, attributes, links, and formatting during repairs.
    • Sanitized repaired list content to prevent unsafe markup.
  • Documentation

    • Added changelog entries describing the Code block font and list-rendering fixes.

zackkatz added 2 commits July 24, 2026 08:53
…mily

The code-block-pro enricher emitted `font-family:<name>` with no generic
family, so a custom CBP font (e.g. Code-Pro-JetBrains-Mono, which is not a
loaded webfont) fell back to the browser default serif. Append the monospace
stack when the value has no generic family keyword, mirroring what the CBP
editor bakes; idempotent, so re-runs never double-append.

Claude-Session: https://claude.ai/code/session_017AMC5bVpEM9G3WtXbXtmwj
Inserting a core/list with its <li> items in the deprecated `values`
attribute and an empty <ul> wrapper (no core/list-item children) saved a
block that renders an empty list on modern WordPress. Add a write-path
normalizer that bakes the items into the wrapper (reconciling ordered vs
unordered), modeled on the core/image normalizer and running at the
non-bypassable gk/block-mcp/block/normalize chokepoint. Idempotent, with
guards that leave correct and genuinely-deprecated lists byte-identical.

Claude-Session: https://claude.ai/code/session_017AMC5bVpEM9G3WtXbXtmwj
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The change adds monospace fallback and wrapper synchronization for Code Block Pro blocks. It also adds normalization for malformed core/list blocks that store list items in deprecated values markup.

Changes

Code Block Pro font synchronization

Layer / File(s) Summary
Font fallback and wrapper synchronization
src/enrichers.ts, wordpress-plugin/gk-block-mcp/assets/mcp-server/index.cjs, wordpress-plugin/gk-block-mcp/readme.txt
Font families receive a generic monospace fallback when needed. Existing wrapper styles and font-family data attributes are synchronized during updates.
Font synchronization validation
src/__tests__/unit/enrichers/cbp-enricher.test.ts
Tests cover fallback normalization, escaping, existing wrapper updates, preserved styles and data attributes, font-only changes, and no-op behavior.

Core list normalization

Layer / File(s) Summary
Malformed core/list repair
wordpress-plugin/gk-block-mcp/includes/block-normalizers/class-core-list-normalizer.php
The normalizer repairs empty list wrappers that contain list items in deprecated values markup. It sanitizes content, preserves attributes and nested markup, and synchronizes block HTML fields.
Normalization validation
wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php
Tests cover persistence flows, idempotence, valid child-block lists, nested markup, ordered-list conversion, sanitization, and malformed wrappers.

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

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies both main fixes: code-block-pro font rendering and core/list values rendering.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/block-render-fidelity

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

zackkatz added 2 commits July 24, 2026 09:36
The embedded dist bundle (assets/mcp-server/index.cjs) is committed and
verified against a fresh build in CI. Rebuild it so it includes the
ensureMonospaceFallback change from the enrichers fix.

Claude-Session: https://claude.ai/code/session_017AMC5bVpEM9G3WtXbXtmwj
The code-block-pro enricher has two innerHTML paths. The fresh-wrapper path
runs ensureMonospaceFallback() on the font stack; the in-place path rewrites
only the <pre> and the copy <textarea>, leaving the wrapper's style attribute
as serialized. Two failures follow for any block that already has markup:

- A fontFamily attribute change saves but never reaches the rendered wrapper.
- A stack with no generic family (a bare `Code-Pro-JetBrains-Mono`) falls back
  to the browser default serif when that webfont is unavailable, so code
  renders in a proportional face.

The early bail-out compounded it: a fontFamily-only edit arrives with identical
codeHTML and language, returned null, and skipped the wrapper entirely.

syncWrapperFontFamily() rewrites just the font-family declaration and the
data-code-block-pro-font-family attribute. It deliberately does not rebuild the
style attribute, which also carries CBP's --cbp-* and --shiki-* custom
properties. The bail-out now attempts the sync before returning null.

Five regression tests; four fail with the fix reverted.

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

🧹 Nitpick comments (4)
wordpress-plugin/gk-block-mcp/includes/block-normalizers/class-core-list-normalizer.php (2)

97-100: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the redundant innerBlocks check.

Line 73 returns early for any block with non-empty innerBlocks. At line 98 the condition is therefore always true. Assign innerContent unconditionally.

♻️ Proposed refactor
 		$block['innerHTML'] = $html;
-		if ( empty( $block['innerBlocks'] ) ) {
-			$block['innerContent'] = array( $html );
-		}
+		// The block is a leaf here (non-empty innerBlocks returned early), so a
+		// single innerContent chunk keeps the null-placeholder invariant.
+		$block['innerContent'] = array( $html );
🤖 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
`@wordpress-plugin/gk-block-mcp/includes/block-normalizers/class-core-list-normalizer.php`
around lines 97 - 100, In the normalizer method containing the
`$block['innerHTML']` assignment, remove the redundant `empty(
$block['innerBlocks'] )` condition and assign `$block['innerContent']` to
`array( $html )` unconditionally, relying on the earlier return for blocks with
non-empty innerBlocks.

38-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Align two details with the project conventions.

  1. Line 41 states that a normalizer loader calls init(), but line 220 registers the filter when the file loads. Describe the actual registration path.
  2. Line 90 puts a function-call result directly in if, and line 198 puts a compound check directly in if. Assign both to named variables. The while ( $processor->next_tag() ) loops are load-bearing and stay as they are.
♻️ Proposed refactor
-		if ( self::contains_li( $html ) ) {
+		$has_li = self::contains_li( $html );
+		if ( $has_li ) {
 			return $block;
 		}
-			if ( isset( $attrs['start'] ) && is_scalar( $attrs['start'] ) ) {
+			$has_start = isset( $attrs['start'] ) && is_scalar( $attrs['start'] );
+			if ( $has_start ) {
 				$processor->set_attribute( 'start', (string) $attrs['start'] );
 			}

As per coding guidelines: "Assign function-call results and compound checks to named variables before using them in if, while, or ternary expressions, except for load-bearing short-circuit guards." and "Comments must describe present-tense behavior and hard contracts only".

Also applies to: 90-90, 198-198

🤖 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
`@wordpress-plugin/gk-block-mcp/includes/block-normalizers/class-core-list-normalizer.php`
around lines 38 - 49, Update the init() docblock to describe the actual
file-load filter registration path instead of claiming a normalizer loader
invokes it. In the relevant normalization logic, assign the function-call result
at line 90 and the compound condition at line 198 to descriptive variables
before their if statements; leave the load-bearing processor->next_tag() while
loops unchanged.

Source: Coding guidelines

wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php (1)

536-552: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extend the ordered-list coverage.

test_ordered_list_with_ul_supplied_emits_ol asserts start only. prepare_wrapper() also applies type and reversed. Add assertions for both so a regression in either branch fails the suite.

💚 Proposed addition
 			array(
 				'values'  => self::INVALID_LIST_VALUES,
 				'ordered' => true,
 				'start'   => 3,
+				'type'    => 'A',
+				'reversed' => true,
 			),
 			self::INVALID_LIST_HTML
 		);
@@
 		$this->assertStringContainsString( 'start="3"', $out[0]['innerHTML'], 'the ordered start attribute must be carried onto the <ol>' );
+		$this->assertStringContainsString( 'type="A"', $out[0]['innerHTML'], 'the ordered type attribute must be carried onto the <ol>' );
+		$this->assertStringContainsString( 'reversed', $out[0]['innerHTML'], 'the reversed attribute must be carried onto the <ol>' );
🤖 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 `@wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php` around
lines 536 - 552, Extend test_ordered_list_with_ul_supplied_emits_ol to configure
ordered-list type and reversed attributes, then assert the normalized innerHTML
contains both corresponding attributes alongside the existing start assertion.
Keep the current wrapper and list-item assertions unchanged.
src/__tests__/unit/enrichers/cbp-enricher.test.ts (1)

569-575: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the synchronized data attribute.

blockWithWrapper() has no data-code-block-pro-font-family attribute. This test only verifies the style declaration. If a change stops adding the data attribute during a font-only update, the test still passes.

Add an assertion for data-code-block-pro-font-family="Menlo,monospace" here or in the font-only update test.

Proposed test assertion
     expect(result.innerHTML).toContain('font-family:Menlo,monospace;font-size:1rem');
+    expect(result.innerHTML).toContain(
+      'data-code-block-pro-font-family="Menlo,monospace"',
+    );
     expect(result.innerHTML).not.toContain('font-family:Code-Pro-JetBrains-Mono;');

Based on PR objectives, the wrapper data attribute must remain synchronized when only fontFamily changes.

🤖 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 `@src/__tests__/unit/enrichers/cbp-enricher.test.ts` around lines 569 - 575,
Add an assertion to the stale-wrapper font-family test around enrichBlock and
blockWithWrapper, verifying the result includes
data-code-block-pro-font-family="Menlo,monospace" alongside the existing style
assertions. Ensure the test covers synchronization when only fontFamily changes.
🤖 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 `@src/enrichers.ts`:
- Around line 265-268: Update ensureMonospaceFallback to detect generic font
families only when they are complete comma-separated entries, not substrings
within custom family names such as custom-monospace-font. Preserve the existing
fallback behavior for lists without a recognized generic family and return the
original fontFamily unchanged when a valid generic entry is present.

In `@wordpress-plugin/gk-block-mcp/assets/mcp-server/index.cjs`:
- Around line 52352-52355: Update ensureMonospaceFallback to parse the
comma-separated fontFamily list, normalize each family entry, and compare
complete entries against the generic family names rather than using substring
matching. Preserve existing generic-family detection and fallback behavior,
while ensuring custom names such as those containing “serif” or “monospace”
still receive the fallback. Add regression coverage for both cases.
- Line 52414: Update the generated-markup style construction around
ensureMonospaceFallback so fontFamily is added only when it is a non-empty,
non-whitespace string, matching the synchronization path’s existing absence
check; otherwise skip the font-family declaration.
- Around line 52363-52375: Validate the string-valued fontFamily attribute as a
permitted CSS font-family value before passing it to ensureMonospaceFallback or
interpolating it into markup. Reject values containing CSS delimiters or
otherwise invalid syntax, preserve the existing fallback behavior for valid
values, and regenerate the bundled index.cjs after updating the source
implementation.

In
`@wordpress-plugin/gk-block-mcp/includes/block-normalizers/class-core-list-normalizer.php`:
- Line 29: Replace every `@since TODO` annotation in the public class and its
`init()` and `normalize()` methods with `@since 2.1.0`, covering all three
documented locations.
- Around line 149-153: Update the fallback branch in the core list normalizer
around $close and $pos to append the missing closing tag to the prepared
$wrapper, returning $wrapper . $close instead of rebuilding the opening tag.
Preserve all attributes and classes applied by prepare_wrapper().
- Around line 94-99: Sanitize the complete HTML returned by
bake_values_into_wrapper() before assigning it to $block['innerHTML'] in the
core list normalizer. Apply the same final-output sanitization on both return
paths, including the fallback when innerBlocks is empty, while leaving
attributes['values'] unchanged.

---

Nitpick comments:
In `@src/__tests__/unit/enrichers/cbp-enricher.test.ts`:
- Around line 569-575: Add an assertion to the stale-wrapper font-family test
around enrichBlock and blockWithWrapper, verifying the result includes
data-code-block-pro-font-family="Menlo,monospace" alongside the existing style
assertions. Ensure the test covers synchronization when only fontFamily changes.

In
`@wordpress-plugin/gk-block-mcp/includes/block-normalizers/class-core-list-normalizer.php`:
- Around line 97-100: In the normalizer method containing the
`$block['innerHTML']` assignment, remove the redundant `empty(
$block['innerBlocks'] )` condition and assign `$block['innerContent']` to
`array( $html )` unconditionally, relying on the earlier return for blocks with
non-empty innerBlocks.
- Around line 38-49: Update the init() docblock to describe the actual file-load
filter registration path instead of claiming a normalizer loader invokes it. In
the relevant normalization logic, assign the function-call result at line 90 and
the compound condition at line 198 to descriptive variables before their if
statements; leave the load-bearing processor->next_tag() while loops unchanged.

In `@wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php`:
- Around line 536-552: Extend test_ordered_list_with_ul_supplied_emits_ol to
configure ordered-list type and reversed attributes, then assert the normalized
innerHTML contains both corresponding attributes alongside the existing start
assertion. Keep the current wrapper and list-item assertions unchanged.
🪄 Autofix

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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 82ef67be-ab90-4366-ac5b-ef1de1107925

📥 Commits

Reviewing files that changed from the base of the PR and between f9859e9 and 0e1bdf3.

📒 Files selected for processing (6)
  • src/__tests__/unit/enrichers/cbp-enricher.test.ts
  • src/enrichers.ts
  • wordpress-plugin/gk-block-mcp/assets/mcp-server/index.cjs
  • wordpress-plugin/gk-block-mcp/includes/block-normalizers/class-core-list-normalizer.php
  • wordpress-plugin/gk-block-mcp/readme.txt
  • wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php

Comment thread src/enrichers.ts
Comment thread wordpress-plugin/gk-block-mcp/assets/mcp-server/index.cjs
Comment thread wordpress-plugin/gk-block-mcp/assets/mcp-server/index.cjs Outdated
Comment thread wordpress-plugin/gk-block-mcp/assets/mcp-server/index.cjs Outdated
/**
* Normalizer for core/list blocks.
*
* @since TODO

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Replace @since TODO with @since 2.1.0.

The class, init(), and normalize() are shipped public members. The project convention requires a concrete version placeholder until the release version is settled.

📝 Proposed fix
- * `@since` TODO
+ * `@since` 2.1.0

Apply the same change at lines 43 and 60.

As per coding guidelines: "Add @since {version} to shipped public production classes, methods, hooks, and REST routes; new code uses @since 2.1.0 until the release version is settled."

Also applies to: 43-43, 60-60

🤖 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
`@wordpress-plugin/gk-block-mcp/includes/block-normalizers/class-core-list-normalizer.php`
at line 29, Replace every `@since TODO` annotation in the public class and its
`init()` and `normalize()` methods with `@since 2.1.0`, covering all three
documented locations.

Source: Coding guidelines

Generic font families are matched as whole comma-separated entries. A
substring test read `Source Serif 4` and `custom-monospace-font` as
already carrying a generic family and withheld the fallback stack those
names most need.

A font-family that is blank or carries CSS structure is now dropped
rather than emitted. escapeAttr stops a value breaking out of the
attribute, but the declaration is spliced among others, so an unescaped
`;` or `{` appended CSS of the caller's choosing.

The list normalizer sanitizes the markup it bakes. `values` is attribute
data and normalization runs after the write path's innerHTML
sanitization, so a `<script>` element or an `onclick` handler supplied
through that attribute reached post_content intact, bypassing
wp_kses_post. The sanitized value is written back to the attribute too,
since Gutenberg matches the values-based deprecation by regenerating
markup from it and the two must agree. Both new tests fail against the
pre-fix normalizer with exactly those symptoms.

An unclosed wrapper now keeps the class and ordered attributes
prepare_wrapper applied, instead of being rebuilt as a bare opening tag.

Also: the redundant innerBlocks re-check, two inlined conditionals, and
ordered-list coverage for type and reversed.

@SInCE is 2.2.1, not the 2.1.0 the review suggested: 2.1.0 shipped
without this class, which is in no tag yet.
@zackkatz

Copy link
Copy Markdown
Member Author

Worked through all 7 findings in 2b86cf4. Six applied, one deviated from, one skipped.

Applied

  • Generic families as complete entries (src/enrichers.ts, bundle regenerated). Switched to splitting the list and comparing normalized entries against a Set rather than a regex, since it reads more directly. Source Serif 4 and custom-monospace-font now get the fallback; a quoted or padded real entry ("Fira Code", monospace) is still recognized, so the helper stays idempotent.
  • Blank / CSS-bearing fontFamily dropped. Both call sites now share one isUsableFontFamily guard, which also covers the absence check the sync path already had.
  • Sanitize what the list normalizer bakes. Confirmed exploitable before fixing: a <script> element and an onclick handler passed through values reached post_content intact. Normalization runs at save_blocks(), after Block_Writer::sanitize_inner_html(), so nothing downstream re-filtered it.
  • Unclosed-wrapper fallback keeps wrapper attributes. One correction to the suggestion: it proposed $wrapper . $close, which drops $values and defeats the bake. Implemented as $wrapper . $values . $close.
  • Redundant innerBlocks check removed, and the two inlined conditionals assigned to named variables.
  • Ordered-list coverage extended with type and reversed.

Deviated: @since

Used 2.2.1, not 2.1.0. v2.1.0 is tagged and shipped without this class — git tag --contains puts the list normalizer in no tag at all. The @since 2.1.0 on the sibling image normalizer is correct because that one did ship in 2.1.0. (The repo's AGENTS.md still names 2.1.0 as the placeholder for new code; that line is stale now that 2.2.0 has shipped.) Happy to move it if the next release is numbered differently.

Skipped: init() docblock wording

"Called once at plugin init by the normalizer loader" is verbatim what the shipped sibling normalizer says, and the loader is what causes the call — register_global_filters() requires each file on plugins_loaded, and the file's last line self-registers. Changing one of two identical docblocks would create drift for no behavioral gain.

On the data attribute

Adding data-code-block-pro-font-family to the blockWithWrapper() test asserts behavior that does not exist: syncWrapperFontFamily replaces that attribute only when the wrapper already carries one, and the full-rebuild path never emits it. The synchronization contract is already pinned by preserves other declarations and custom properties in the style attribute, whose fixture does carry it. Rather than assert an invented behavior, I seeded the wrapper in still updates when only fontFamily changed with the attribute, so the font-only path — the one the objective names — now pins that it tracks the new value and drops the old.

Note on an existing test

escapes double-quotes in style attribute values passed Arial" onerror="alert(1), which the new guard rejects outright for its parens, so nothing was left to escape. Changed the input to Arial" onerror="alert so it still exercises the escaping path; the drop path is covered separately.

Verification

New tests were confirmed to fail against the pre-fix normalizer with the exact symptoms (script and handler in post_content; start="5" and the wrapper class discarded). Gates: PHPUnit 1457 green, phpcs 0/0, PHPStan OK, Vitest 899 green, tsc --noEmit clean, bundle regenerated.

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

🧹 Nitpick comments (1)
wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php (1)

393-403: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace change-history comments with current contracts.

Keep comments limited to the malformed input and required normalized output.

  • wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php#L393-L403: describe the input shape and the required baked wrapper result. Remove reported-issue and author-history text.
  • wordpress-plugin/gk-block-mcp/includes/block-normalizers/class-core-list-normalizer.php#L16-L17: state the values and baked-markup synchronization invariant. Remove next-edit rationale.
  • wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php#L558-L570: state that stored and attribute values must be sanitized. Remove bypass chronology.
  • wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php#L599-L605: state the required result for an unclosed wrapper. Remove prior fallback behavior.

As per coding guidelines: “Comments must describe present-tense behavior and hard contracts only; omit history, journal entries, off-tree specification pointers, and future-architecture speculation.”

🤖 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 `@wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php` around
lines 393 - 403, Revise only the comments at
wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php:393-403 to
describe the malformed core/list input and required baked-wrapper output; at
wordpress-plugin/gk-block-mcp/includes/block-normalizers/class-core-list-normalizer.php:16-17
to state the values/markup synchronization invariant; at
wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php:558-570 to
state that stored and attribute values are sanitized; and at
wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php:599-605 to
state the required result for an unclosed wrapper. Remove issue history, author
rationale, next-edit rationale, bypass chronology, and prior fallback behavior
while preserving present-tense contracts tied to the relevant core-list
normalizer and tests.

Source: Coding guidelines

🤖 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
`@wordpress-plugin/gk-block-mcp/includes/block-normalizers/class-core-list-normalizer.php`:
- Around line 165-168: Update the closing-tag detection in the current
normalizer method and prepare_wrapper() to match valid list closing tags with
optional whitespace before “>”, preserving the existing wrapper attributes and
ensuring replacement tags remain balanced. Add coverage for a wrapper ending in
</ul > and verify list items are inserted before the closing tag.
- Around line 103-108: Update the normalization flow around
Block_Writer::sanitize_inner_html and bake_values_into_wrapper so it returns the
original block unchanged when sanitized $values contains no <li> element, even
if the string is non-empty. Preserve the existing list-processing path when at
least one list item remains, and add a regression test covering a non-list
fragment.

---

Nitpick comments:
In `@wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php`:
- Around line 393-403: Revise only the comments at
wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php:393-403 to
describe the malformed core/list input and required baked-wrapper output; at
wordpress-plugin/gk-block-mcp/includes/block-normalizers/class-core-list-normalizer.php:16-17
to state the values/markup synchronization invariant; at
wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php:558-570 to
state that stored and attribute values are sanitized; and at
wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php:599-605 to
state the required result for an unclosed wrapper. Remove issue history, author
rationale, next-edit rationale, bypass chronology, and prior fallback behavior
while preserving present-tense contracts tied to the relevant core-list
normalizer and tests.
🪄 Autofix

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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b1f2cdca-ff5f-4c07-b8a9-b2f997aea9b1

📥 Commits

Reviewing files that changed from the base of the PR and between 0e1bdf3 and 2b86cf4.

📒 Files selected for processing (6)
  • src/__tests__/unit/enrichers/cbp-enricher.test.ts
  • src/enrichers.ts
  • wordpress-plugin/gk-block-mcp/assets/mcp-server/index.cjs
  • wordpress-plugin/gk-block-mcp/includes/block-normalizers/class-core-list-normalizer.php
  • wordpress-plugin/gk-block-mcp/readme.txt
  • wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php
🚧 Files skipped from review as they are similar to previous changes (3)
  • wordpress-plugin/gk-block-mcp/assets/mcp-server/index.cjs
  • wordpress-plugin/gk-block-mcp/readme.txt
  • src/enrichers.ts

Three defects in the values-baking path, each with a regression test that
fails when the fix is reverted:

- `values` holding no <li> was spliced into the wrapper regardless, emitting a
  list whose children are not list items. The guard runs on the SANITIZED
  value, since a fragment can lose its only <li> to wp_kses_post().
- An end tag may carry whitespace before its `>`, so the literal `</ul>`
  search missed `</ul >`. bake_values_into_wrapper() then appended the items
  after the existing closing tag, outside the list, and prepare_wrapper()
  renamed an opening tag without its matching close, leaving `<ol …></ul >`.
  find_last_closing_tag() matches whitespace-tolerantly for both.
- Only the `values` fragment was sanitized. The composed wrapper is now
  sanitized as a whole, so markup carried by the wrapper itself cannot reach
  post_content. Sanitization is idempotent, so the baked fragment still
  matches the attribute Gutenberg regenerates from.

composer lint 0/0, composer analyze [OK], composer test green (1462/28/11/5).

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

🧹 Nitpick comments (1)
wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php (1)

625-631: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove historical bug narratives from these docblocks.

Describe the current input-output contract only. Remove statements about prior checks, fallback behavior, and earlier malformed output.

  • wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php#L625-L631: State that non-list values content does not change the wrapper.
  • wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php#L644-L647: State that sanitized content without <li> elements does not change the wrapper.
  • wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php#L661-L666: State that a whitespace-bearing closing tag keeps baked items inside the wrapper.
  • wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php#L686-L691: State that ordered-list reconciliation updates both wrapper tags.
  • wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php#L709-L713: State that normalization sanitizes the composed wrapper markup.

As per coding guidelines, comments must “describe present-tense behavior and hard contracts only” and omit historical narratives.

🤖 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 `@wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php` around
lines 625 - 631, Update the docblocks for the affected BlockNormalizer tests to
describe only current input-output contracts: at
wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php lines 625-631,
state that non-list values content leaves the wrapper unchanged; lines 644-647,
state that sanitized content without li elements leaves it unchanged; lines
661-666, state that a whitespace-bearing closing tag keeps baked items inside
the wrapper; lines 686-691, state that ordered-list reconciliation updates both
wrapper tags; and lines 709-713, state that normalization sanitizes the composed
wrapper markup. Remove all historical explanations about prior checks, fallback
behavior, or malformed output.

Source: Coding guidelines

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

Nitpick comments:
In `@wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php`:
- Around line 625-631: Update the docblocks for the affected BlockNormalizer
tests to describe only current input-output contracts: at
wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php lines 625-631,
state that non-list values content leaves the wrapper unchanged; lines 644-647,
state that sanitized content without li elements leaves it unchanged; lines
661-666, state that a whitespace-bearing closing tag keeps baked items inside
the wrapper; lines 686-691, state that ordered-list reconciliation updates both
wrapper tags; and lines 709-713, state that normalization sanitizes the composed
wrapper markup. Remove all historical explanations about prior checks, fallback
behavior, or malformed output.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0166407a-9606-41a6-839a-165fdf558d5b

📥 Commits

Reviewing files that changed from the base of the PR and between 2b86cf4 and caa3e3b.

📒 Files selected for processing (2)
  • wordpress-plugin/gk-block-mcp/includes/block-normalizers/class-core-list-normalizer.php
  • wordpress-plugin/gk-block-mcp/tests/Block/BlockNormalizerTest.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • wordpress-plugin/gk-block-mcp/includes/block-normalizers/class-core-list-normalizer.php

…idelity

# Conflicts:
#	wordpress-plugin/gk-block-mcp/readme.txt
@zackkatz
zackkatz merged commit 8b410e9 into develop Aug 10, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant