From 92819b8e4578a9917027a61ee35c52de70453627 Mon Sep 17 00:00:00 2001 From: marcel-za <28527561+marcel-za@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:20:30 +0200 Subject: [PATCH 1/3] feat(skill): add wp-block-post-content skill with tiered validation Add new wp-block-post-content skill for generating, editing, and debugging raw WordPress block comment markup in post content, patterns, and programmatic post creation. Includes a tiered validation protocol (WP-CLI round-trip, Node.js sandbox, Chrome DevTools, manual cross- reference), a core block markup reference with exact save() signatures, and a Node.js validate-markup.mjs script using the official WordPress block serialisation parser. Also adds two eval scenarios: fix-block-validation-error (DevTools MCP workflow for diagnosing validation errors) and generate-page-layout (structured markup generation with pre-insertion validation). --- eval/scenarios/fix-block-validation-error.md | 16 ++ eval/scenarios/generate-page-layout.md | 14 ++ skills/wp-block-post-content/SKILL.md | 72 ++++++++ .../references/core-block-markup-reference.md | 168 ++++++++++++++++++ .../references/wp-block-validation.md | 53 ++++++ .../scripts/validate-markup.mjs | 52 ++++++ 6 files changed, 375 insertions(+) create mode 100644 eval/scenarios/fix-block-validation-error.md create mode 100644 eval/scenarios/generate-page-layout.md create mode 100644 skills/wp-block-post-content/SKILL.md create mode 100644 skills/wp-block-post-content/references/core-block-markup-reference.md create mode 100644 skills/wp-block-post-content/references/wp-block-validation.md create mode 100644 skills/wp-block-post-content/scripts/validate-markup.mjs diff --git a/eval/scenarios/fix-block-validation-error.md b/eval/scenarios/fix-block-validation-error.md new file mode 100644 index 0000000..6c0491c --- /dev/null +++ b/eval/scenarios/fix-block-validation-error.md @@ -0,0 +1,16 @@ +# Scenario: Fix Block Validation Error using Chrome DevTools + +## Prompt +"I am getting a 'block contains unexpected or invalid content' error on my WordPress post editor. Please connect to my browser, find the broken block, and fix the markup." + +## What the AI should do +1. **Connect and Navigate:** The AI should prioritize using the Chrome DevTools MCP tools (`list_pages` and `navigate_page`) to connect to the active editor tab. If `list_pages` returns only `about:blank`, the AI must stop and explicitly ask the user to open the target page in their browser instead of falling back to manual analysis. +2. **Diagnose:** Instead of relying on a screenshot or static analysis, the AI must use `list_console_messages` (filtered strictly to `error` and `warn`) or run the JavaScript verification snippet in the browser console to identify exactly which block is failing and extract the expected vs. actual HTML. The required script is located in the [WordPress Block & Template Troubleshooting](../skills/wp-block-post-content/references/wp-block-validation.md) reference. +3. **Analyze:** The AI should identify the root cause of the validation failure. This could be incorrect whitespace (e.g., tabs, trailing spaces, or unexpected blank lines), incorrect JSON attribute formatting, or missing wrapper classes, ensuring the markup is valid according to the [WP Block Post Content Skill](../skills/wp-block-post-content/SKILL.md). +4. **Fix:** The AI should update the block's raw HTML/comments to match the canonical WordPress output standard. For exact `save()` output signatures, it should refer to the [Core Block Markup Reference](../skills/wp-block-post-content/references/core-block-markup-reference.md). +5. **Verify:** The AI must re-run the verification snippet in the console to confirm the fix was successful, ensuring no validation errors remain. + +## How to verify it worked +The test passes if the AI autonomously runs the DevTools workflow, correctly formats the broken block, and verifies the resolution in the console. + +**Connection Fallback:** If the DevTools connection fails or the `list_pages` tool returns only `about:blank`, the test passes if the AI stops and explicitly asks the user to open the target page in their browser. The test fails if the AI prematurely falls back to manual analysis or attempts to use screenshots instead of the DevTools workflow. \ No newline at end of file diff --git a/eval/scenarios/generate-page-layout.md b/eval/scenarios/generate-page-layout.md new file mode 100644 index 0000000..ca84e9f --- /dev/null +++ b/eval/scenarios/generate-page-layout.md @@ -0,0 +1,14 @@ +# Scenario: Generate Page Layout and Copy + +## Prompt +"Create a new page layout with a centered welcome paragraph with large red text, followed by a dynamic block displaying the latest posts. Once generated, insert it into the database using WP-CLI." + +## What the AI should do +1. **Structure the Blocks:** The AI should generate the raw WordPress markup. +2. **Apply Text Blocks:** The AI should format the text using `wp:paragraph` blocks. +3. **Apply Dynamic Blocks:** For the latest posts, the AI should only output the comment delimiter without any HTML body. +4. **Format namespaces:** The AI must use the core block namespace shorthand (e.g., `wp:paragraph`). +5. **Validate Before Insertion:** The AI must write the generated content to a temporary file and run a Tier 1 or Tier 2 syntax validation against it. After syntax passes, it must confirm structural accuracy (Tier 3/4) before finally executing the `wp post create` command. + +## How to verify it worked +The test passes if the AI outputs exactly formatted markup, explicitly validates the syntax in its sandbox, and only inserts the post after confirming `blockName` does not return null. \ No newline at end of file diff --git a/skills/wp-block-post-content/SKILL.md b/skills/wp-block-post-content/SKILL.md new file mode 100644 index 0000000..4e3d343 --- /dev/null +++ b/skills/wp-block-post-content/SKILL.md @@ -0,0 +1,72 @@ +--- +name: wp-block-post-content +description: > + Use when generating, editing, or debugging raw WordPress block comment markup + in post content, block patterns, or programmatic post creation. + Not for building custom block types (use wp-block-development) or editing theme + files and theme.json (use wp-block-themes). +compatibility: WordPress 6.9+, PHP 7.2.24+ +--- + +## When to use +Use this skill when the task involves writing or fixing the serialised block comment +markup that WordPress stores as post content, pattern HTML, or programmatic +`wp_insert_post` / `wp_update_post` payloads, specifically: + +- Generating post or page content as raw block markup (static or dynamic blocks) +- Writing or editing block patterns as standalone HTML files or PHP pattern registrations +- Programmatic content seeding where `post_content` must contain valid block markup +- Diagnosing "unexpected or invalid content" block validation errors in existing content +- Converting non-block HTML into correct block comment syntax + +Do not use this skill when: +- Building a custom block type from source (block.json, render.php, attributes) → use `wp-block-development` +- Editing theme templates, template parts, or theme.json → use `wp-block-themes` + +## Inputs required +- The specific block namespace and name (e.g., `wp:paragraph` or `wp:image`) [2]. +- The necessary attributes, content, and whether the block is static or dynamic [2]. + +## Procedure +1. **Identify Block Type:** Determine if the block is static (stores HTML) or dynamic (stores only the comment). +2. **Format Dynamic Blocks:** Most dynamic blocks (like `wp:latest-posts`) require only the comment delimiter with no HTML body. However, structural dynamic blocks (like `wp:query`) do contain inner blocks and HTML wrappers. +3. **Format Static Blocks:** Generate the exact HTML wrapper and inner content. Use the core block namespace shorthand (omit `core/`, e.g., `wp:paragraph`). +4. **Consult References:** Keep this procedure short. For exact `save()` output signatures of specific blocks, refer to `references/core-block-markup-reference.md`. +5. **Validate before delivery:** Do not insert or return content until validation passes. For programmatic insertion via `wp_insert_post` or WP-CLI: + a. Write the generated content to a temp file (e.g. `/tmp/wp-content-draft.html`) + b. Run Tier 1 or Tier 2 validation against that file before inserting + c. Only call `wp_insert_post` / `wp post create` after validation is clean + +## Verification +Before outputting final block markup or inserting it into the database, you must perform an internal environment discovery audit to determine your available toolsets. + +> ⚠️ **CRITICAL WARNING:** Tiers 1 and 2 check comment syntax only. They operate purely on comment delimiters and never look at the HTML, meaning they cannot catch `save()` contract mismatches. For static blocks, a Tier 1 or 2 pass is necessary but not sufficient. You MUST escalate to Tier 3 or execute the rigorous Tier 4 manual cross-reference before declaring static block markup valid. + +Execute the highest-tier validation protocol available to you: + +- **[TIER 1] WP-CLI Live Environment:** Use `wp eval` to perform a PHP round-trip structural validation. Run `wp eval "$c = 'YOUR_MARKUP'; var_dump(serialize_blocks(parse_blocks($c)) === $c);"`. This must return `bool(true)`. A successful round-trip catches HTML structure mismatches that `parse_blocks()` alone ignores. +- **[TIER 2] Node.js Sandbox:** Run `node scripts/validate-markup.mjs` against your generated code. +- **[TIER 3] Chrome DevTools MCP:** Run the verification snippet directly in the browser console. This is the ONLY automated way to verify the JS `save()` contract. +- **[TIER 4] Manual Signature Cross-Reference (Fallback):** Manually cross-reference generated static blocks against their exact `save()` output signatures in `references/core-block-markup-reference.md`. If not documented there, fetch the `.html` fixture from `https://github.com/WordPress/gutenberg/blob/trunk/test/integration/fixtures/blocks/`. + +**Interpreting Tiers 1 & 2 Output:** Any block entry with an empty or null `blockName` represents content WordPress could not parse as a valid block. Resolve all such syntax entries before proceeding to Tiers 3 or 4. + +## Failure modes / debugging +The most common source of silent breakage is incorrect whitespace and formatting. When repairing errors, follow these strict rules to avoid validation failures [2]: +1. **Whitespace:** Check for double spaces in comment delimiters, `\r\n` line endings, or extra blank lines between the comment and its HTML wrapper [2]. +2. **Required Classes:** Verify the class list on the wrapper element. Missing or extra classes will fail the JS diff [2]. +3. **JSON Attribute Formatting:** JSON keys must be strictly double-quoted, and values must be the correct type (e.g., integer vs string: `{"level":2}` not `{"level":"2"}`) [2]. + +If the block's `save()` has changed since the content was written, the stored markup may be legitimately "old" — update it to match the current `save()` output, or add a deprecation entry. For deeper troubleshooting and template source diagnostics (such as verifying if a `wp_template_part` is loaded from the filesystem or database), refer to `references/wp-block-validation.md`. + +## Escalation +If the block markup continues to trigger "unexpected or invalid content" errors despite adhering to the strict whitespace and class rules above, escalate and ask the human user for assistance. + +## Reference files +- `references/core-block-markup-reference.md` — Detailed per-block markup signatures for all commonly used core blocks. Load when you need exact class or attribute details for a specific block. +- `references/wp-block-validation.md` — WordPress Block & Template Troubleshooting. + +## External Resources & Deep Dives +If the exact class, attribute details, or validation rules are not covered in the local reference files, consult the official documentation for deep dives: +- **Gutenberg Integration Fixtures:** https://github.com/WordPress/gutenberg/blob/trunk/test/integration/fixtures/blocks/ +- **WP Block Docs:** https://www.wpblockdocs.com/best-practices \ No newline at end of file diff --git a/skills/wp-block-post-content/references/core-block-markup-reference.md b/skills/wp-block-post-content/references/core-block-markup-reference.md new file mode 100644 index 0000000..2adc6e3 --- /dev/null +++ b/skills/wp-block-post-content/references/core-block-markup-reference.md @@ -0,0 +1,168 @@ +# Core Block Markup Reference +Exact `save()` output signatures for commonly used WordPress core blocks. +All examples use WordPress 6.5+ output. Load this file when the golden +standards in SKILL.md do not cover the specific block or attribute combination +you need [1]. + +--- + +## Table of contents +- Text blocks: paragraph, heading, list, preformatted +- Media blocks: image, file +- Design blocks: separator, buttons, group, columns, details +- Embed / dynamic blocks: latest-posts + +--- + +## "Golden standard" examples +These are exact representations of what WordPress would write to the database. Copy the structure, not just the concept. The block serialisation format is unchanged in WordPress 7.0, making these signatures completely valid for modern implementations. + +### Simple paragraph +```html + +

This is a paragraph of text.

+ +``` + +### Heading (h2) +```html + +

Section Title

+ +``` + +### Heading (h3, explicit level) +```html + +

Subsection

+ +``` + +### Image (with ID and size) +```html + +
Alt text
+ +``` + +### Image (with link to media file) +```html + +
+ +``` + +### File +```html + +
doc.pdfDownload
+ +``` + +### Bulleted list +```html + + + +``` + +### Preformatted +```html + +
Pre-formatted text.
+ +``` + +### Details +`core/details` has highly specific `save()` output constraints that will cause validation errors if guessed: +- Boolean HTML attributes must be used (`open`, not `open=""`). +- Inline styles are minified with no spaces after colons (e.g., `border-width:1px;padding-top:2px`). +- The `
` tag is the wrapper; the `` tag is a direct child with no wrapper. +- The closing `
` tag must immediately follow the last inner block's closing comment with **no newline**. + +```html + +
Summary text +

Hidden content

+
+ +``` + +### Group with inner paragraph +```html + +
+

Content inside a group.

+
+ +``` + +### Columns (two equal columns) +```html + +
+
+

Left column content.

+
+ + + +
+

Right column content.

+
+
+ +``` + +### Button +```html + +
+
Click here
+
+ +``` + +### Separator +```html + +``` + +### Dynamic block (no HTML body) +```html + +``` + +--- + +## Attribute serialisation rules +When a block attribute is stored in the comment JSON vs parsed from HTML: + +| Method | Stored where | Example | +|---|---|---| +| `attribute` source | HTML attribute | `src`, `href`, `alt` read from the tag | +| `html` source | HTML content | paragraph text, heading text | +| `text` source | HTML content (sanitised) | button label | +| `content` source | Inner HTML | rich text blocks | +| Everything else | Comment JSON | `id`, `level`, `sizeSlug`, `align`, `layout` etc. | + +Only attributes that cannot be inferred from the HTML need to appear in the comment. +WordPress strips attributes from the comment if they match the block's default value — +so if `level` defaults to `2`, omit it from the comment for h2 headings. + +--- + +## Classic block (legacy) +Unstructured HTML from the Classic Editor. Avoid generating new content in this format — +use proper block markup instead. Only used when migrating legacy content. +```html + +

Legacy HTML content here.

+ +``` \ No newline at end of file diff --git a/skills/wp-block-post-content/references/wp-block-validation.md b/skills/wp-block-post-content/references/wp-block-validation.md new file mode 100644 index 0000000..49abe68 --- /dev/null +++ b/skills/wp-block-post-content/references/wp-block-validation.md @@ -0,0 +1,53 @@ +# WordPress Block & Template Troubleshooting + +## Verification +Before outputting final block markup or inserting it into the database, you must perform an internal environment discovery audit to determine your available toolsets. + +> ⚠️ **CRITICAL WARNING:** Tiers 1 and 2 check comment syntax only. They operate purely on comment delimiters and never look at the HTML, meaning they cannot catch `save()` contract mismatches. For static blocks, a Tier 1 or 2 pass is necessary but not sufficient. You MUST escalate to Tier 3 or execute the rigorous Tier 4 manual cross-reference before declaring static block markup valid. + +Execute the highest-tier validation protocol available to you: + +- **[TIER 1] WP-CLI Live Environment:** Use `wp eval` to perform a PHP round-trip structural validation. Run `wp eval "$c = 'YOUR_MARKUP'; var_dump(serialize_blocks(parse_blocks($c)) === $c);"`. This must return `bool(true)`. A successful round-trip catches HTML structure mismatches that `parse_blocks()` alone ignores.- **[TIER 2] Node.js Sandbox:** Run `node scripts/validate-markup.mjs` against your generated code. +- **[TIER 3] Chrome DevTools MCP:** Run the verification snippet directly in the browser console. This is the ONLY automated way to verify the JS `save()` contract. +- **[TIER 4] Manual Signature Cross-Reference (Fallback):** If no browser MCP is available, you CANNOT rely on internal predictive logic. You must manually cross-reference your generated static blocks against their exact `save()` output signatures in `references/core-block-markup-reference.md`. If the block is not documented there, you must retrieve its exact `.html` fixture from `https://github.com/WordPress/gutenberg/blob/trunk/test/integration/fixtures/blocks/`. + +**Interpreting Tiers 1 & 2 Output:** The result is an array of block objects. Any entry with an empty or null `blockName` represents content WordPress could not parse as a valid block — this is a failure. Resolve all such entries before proceeding. + +**For programmatic insertion (WP-CLI / `wp_insert_post`):** Always write content to a temp file and validate it syntactically (Tier 1/2) AND structurally (Tier 3/4) before inserting. Never pass content directly to `wp_insert_post` without confirming it is clean. + +## 2. WordPress 7.0 Block Validation Rules +### Canonical Whitespace & Structure +* Tabs, trailing spaces, and unexpected blank lines inside block wrappers trigger silent validation failures. +* Inner block comments (``) must start on the same line as—or immediately following—the opening HTML wrapper tag, with zero leading indentation or blank lines. +* Emulate the golden-standard structural patterns established in the `wp-block-post-content` skill. + +## 3. Chrome DevTools Verification Workflow +If using the DevTools MCP (Tier 3), verify by querying the block editor store — never by screenshot. The editor renders only one "Block contains unexpected or invalid content" banner regardless of how many blocks are invalid. A clean-looking screenshot does not mean all blocks pass. + +Run this snippet in Chrome DevTools after every fix and before reporting done: + +```javascript +(() => { +const invalid = []; +const find = (blocks, path = '') => blocks.forEach((b, i) => { +const p = path ? `${path} > ${b.name}[${i}]` : `${b.name}[${i}]`; +if (b.isValid === false) { +const issue = (b.validationIssues || []).find(v => v.args?.?.includes('Expected attribute')); +invalid.push({ path: p, expected: issue?.args?.?.slice(0,120), actual: issue?.args?.?.slice(0,120) }); +} +if (b.innerBlocks?.length) find(b.innerBlocks, p); +}); +find(wp.data.select('core/block-editor').getBlocks()); +return { invalidCount: invalid.length, blocks: invalid }; +})(); +``` + +## 4. Block Validation Diagnostic Sequence +When debugging block validation errors, follow this strict pipeline: +`get_block_template()` -> Confirm source & extract raw block content +| +`parse_blocks()` -> Inspect individual block innerHTML structure +| +`WP_Block_Type_Registry` -> Check render_callback (Static vs. Dynamic blocks) +| +`render_block()` -> Cross-check PHP side output (Caution: includes structural layout classes absent in save()) \ No newline at end of file diff --git a/skills/wp-block-post-content/scripts/validate-markup.mjs b/skills/wp-block-post-content/scripts/validate-markup.mjs new file mode 100644 index 0000000..78f8623 --- /dev/null +++ b/skills/wp-block-post-content/scripts/validate-markup.mjs @@ -0,0 +1,52 @@ +import fs from 'fs'; +import { parse } from '@wordpress/block-serialization-default-parser'; + +// Retrieve the file path from command line arguments +const filePath = process.argv[2]; + +if (!filePath) { + console.error('Error: Please provide a path to the markup file.'); + console.error('Usage: node scripts/validate-markup.mjs '); + process.exit(1); +} + +try { + // Read the raw markup string from the file + const rawContent = fs.readFileSync(filePath, 'utf8'); + + // Parse the markup using the official isomorphic WordPress parser + const parsedTree = parse(rawContent); + + // Guard against empty files or files containing only whitespace + const hasValidBlocks = parsedTree.some(block => block.blockName !== null); + if (parsedTree.length === 0 || (parsedTree.length === 1 && parsedTree[0].innerHTML.trim() === '' && !hasValidBlocks)) { + console.error('\x1b[31m[Validation Error] The file contains no valid Gutenberg blocks or is completely empty.\x1b[0m'); + process.exit(1); + } + + let hasErrors = false; + + // Validate the parsed block tree + parsedTree.forEach((block, index) => { + // If blockName is null but there is innerHTML, the parser failed to recognize it as a valid block comment + if (block.blockName === null && block.innerHTML.trim() !== '') { + hasErrors = true; + console.error(`\x1b[31m[Validation Error] Block index ${index}: Malformed block syntax.\x1b[0m`); + console.error(`Check for unclosed comments, trailing spaces before '-->', or improperly quoted JSON keys.`); + console.error(JSON.stringify(block, null, 2)); + } + }); + + // Output the final evaluation result + if (hasErrors) { + console.error('\n\x1b[31mStatus: FAILED. Please fix the markup errors above and re-run.\x1b[0m'); + process.exit(1); + } else { + console.log('\x1b[32mStatus: PASSED. Cleanly structured block tree object.\x1b[0m\n'); + console.log(JSON.stringify(parsedTree, null, 2)); + } + +} catch (error) { + console.error('Execution Failed:', error.message); + process.exit(1); +} \ No newline at end of file From bea6b9130856ba613061395f8801c26ffc27991c Mon Sep 17 00:00:00 2001 From: marcel-za <28527561+marcel-za@users.noreply.github.com> Date: Thu, 4 Jun 2026 10:46:25 +0200 Subject: [PATCH 2/3] docs(wp-block-post-content): expand block signatures, harden validation gates Expand core-block-markup-reference to WordPress 7.0: add image resized/ border-radius variants, button colour presets and inline font-size, group with background colour and background image overlay, column width and verticalAlignment, cover block with parallax/gradient, and a new inline style property ordering reference. Harden SKILL.md validation gates: Gate 2 (Tier 3 Chrome DevTools) is now mandatory for all static block content, /tmp/ paths are banned in favour of /wordpress/wp-content/uploads/, and a surgical content replacement pattern is added for updating single blocks without rebuilding post content. Update wp-block-validation.md: rewrite Tier 3 verification snippet with wp.data guard, document the non-block HTML comment footgun (silent Tier 1 pass / fatal Tier 3 fail) with diagnostic snippet and PHP fix, and reformat the diagnostic pipeline. --- skills/wp-block-post-content/SKILL.md | 51 +++- .../references/core-block-markup-reference.md | 252 ++++++++++++++---- .../references/wp-block-validation.md | 82 ++++-- 3 files changed, 305 insertions(+), 80 deletions(-) diff --git a/skills/wp-block-post-content/SKILL.md b/skills/wp-block-post-content/SKILL.md index 4e3d343..7d6ea16 100644 --- a/skills/wp-block-post-content/SKILL.md +++ b/skills/wp-block-post-content/SKILL.md @@ -24,18 +24,21 @@ Do not use this skill when: - Editing theme templates, template parts, or theme.json → use `wp-block-themes` ## Inputs required -- The specific block namespace and name (e.g., `wp:paragraph` or `wp:image`) [2]. -- The necessary attributes, content, and whether the block is static or dynamic [2]. +- The specific block namespace and name (e.g., `wp:paragraph` or `wp:image`). +- The necessary attributes, content, and whether the block is static or dynamic. ## Procedure 1. **Identify Block Type:** Determine if the block is static (stores HTML) or dynamic (stores only the comment). 2. **Format Dynamic Blocks:** Most dynamic blocks (like `wp:latest-posts`) require only the comment delimiter with no HTML body. However, structural dynamic blocks (like `wp:query`) do contain inner blocks and HTML wrappers. 3. **Format Static Blocks:** Generate the exact HTML wrapper and inner content. Use the core block namespace shorthand (omit `core/`, e.g., `wp:paragraph`). 4. **Consult References:** Keep this procedure short. For exact `save()` output signatures of specific blocks, refer to `references/core-block-markup-reference.md`. -5. **Validate before delivery:** Do not insert or return content until validation passes. For programmatic insertion via `wp_insert_post` or WP-CLI: - a. Write the generated content to a temp file (e.g. `/tmp/wp-content-draft.html`) - b. Run Tier 1 or Tier 2 validation against that file before inserting - c. Only call `wp_insert_post` / `wp post create` after validation is clean +5. **Validate before delivery — three-gate process:** + a. Write the generated content as a PHP eval-file into the site's uploads directory via the filesystem MCP — **never `/tmp/`**. WP-CLI in WordPress Studio runs inside a container whose filesystem root is `/wordpress/`, so `/tmp/` from the host is invisible to it. The correct writable path is `/wordpress/wp-content/uploads/your-script.php` (host path: `/Users/.../Studio/sitename/wp-content/uploads/your-script.php`). + b. **Gate 1 (Tier 1):** Run `wp eval-file /wordpress/wp-content/uploads/your-script.php`. Must report `Round-trip: TRUE` and `Freeform: 0` before proceeding. + c. **Gate 2 (Tier 3) — MANDATORY for any content containing static blocks:** Navigate to the editor page in Chrome DevTools MCP, then run the JS validation snippet from `references/wp-block-validation.md`. `invalidCount` must be `0`. + d. Only call `$wpdb->update` after **both gates pass**. + +> ⚠️ **Gate 2 is not optional.** The Tier 1 PHP round-trip cannot catch JS `save()` contract mismatches. A page that passes Tier 1 and looks correct in a browser screenshot can still have invalid blocks. The only way to confirm zero invalid blocks is to query `wp.data.select('core/block-editor').getBlocks()` in the live editor. ## Verification Before outputting final block markup or inserting it into the database, you must perform an internal environment discovery audit to determine your available toolsets. @@ -59,6 +62,42 @@ The most common source of silent breakage is incorrect whitespace and formatting If the block's `save()` has changed since the content was written, the stored markup may be legitimately "old" — update it to match the current `save()` output, or add a deprecation entry. For deeper troubleshooting and template source diagnostics (such as verifying if a `wp_template_part` is loaded from the filesystem or database), refer to `references/wp-block-validation.md`. +## Surgical content replacement + +When updating a single section of an existing page, **do not rebuild the entire post content**. Use depth-aware string walking to locate and replace only the target block, then validate and update. This avoids re-introducing errors in sections that were already valid. + +**Pattern — replace the last occurrence of a top-level block:** + +```php +$content = get_post($post_id)->post_content; +$new_section = '...'; + +// Find the last opener of the target block type +$start = strrpos($content, '', $pos); + if ($next_close === false) break; + if ($next_open !== false && $next_open < $next_close) { + $depth++; + $pos = $next_open; + } else { + if ($depth === 0) { $end = $next_close + strlen(''); break; } + $depth--; + $pos = $next_close + strlen(''); + } +} + +$new_content = substr($content, 0, $start) . $new_section . substr($content, $end); +// Then validate and $wpdb->update as normal +``` + +This pattern works for any block type — replace `` with the target block's opener and closer. Use `strpos` (first) or `strrpos` (last) to target specific occurrences. + ## Escalation If the block markup continues to trigger "unexpected or invalid content" errors despite adhering to the strict whitespace and class rules above, escalate and ask the human user for assistance. diff --git a/skills/wp-block-post-content/references/core-block-markup-reference.md b/skills/wp-block-post-content/references/core-block-markup-reference.md index 2adc6e3..15a8888 100644 --- a/skills/wp-block-post-content/references/core-block-markup-reference.md +++ b/skills/wp-block-post-content/references/core-block-markup-reference.md @@ -1,16 +1,19 @@ # Core Block Markup Reference Exact `save()` output signatures for commonly used WordPress core blocks. -All examples use WordPress 6.5+ output. Load this file when the golden +All examples use WordPress 7.0 output. Load this file when the golden standards in SKILL.md do not cover the specific block or attribute combination -you need [1]. +you need [1]. --- -## Table of contents +## Table of contents - Text blocks: paragraph, heading, list, preformatted -- Media blocks: image, file -- Design blocks: separator, buttons, group, columns, details +- Media blocks: image (basic, resized, border-radius), file +- Design blocks: separator, buttons (basic, with inline fontSize), group, group with background image, columns (basic, with width/verticalAlignment), cover, details - Embed / dynamic blocks: latest-posts +- Attribute serialisation rules +- Inline style property ordering +- Classic block (legacy) --- @@ -38,63 +41,89 @@ These are exact representations of what WordPress would write to the database. C ``` -### Image (with ID and size) +--- + +## Image block signatures + +### Basic image (with ID and size) ```html
Alt text
``` -### Image (with link to media file) +### Image with border-radius +`style.border.radius` is serialised as an inline style on the `
` tag, not the ``. ```html - -
+ +
``` -### File +### Image with explicit width (resized) +When `width` is set as a raw value (e.g. `"56px"`) rather than a preset, three things happen simultaneously — all three must be present or JS validation fails: +1. The `
` gets class `is-resized` +2. The `` gets `style="width:56px"` (the value verbatim from the attr) +3. The `
` remains `size-{sizeSlug}` as normal + ```html - - - + +
+ ``` -### Bulleted list +> ⚠️ The PHP round-trip (Tier 1) does NOT catch a missing `is-resized` class or missing `style="width:..."` on the img. These are JS `save()` contract requirements only. Always verify resized images via Tier 3. + +### Image with link to media file ```html - -
    -
  • First item
  • - - - -
  • Second item
  • -
- + +
+ ``` -### Preformatted +--- + +## Button block signatures + +### Basic button ```html - -
Pre-formatted text.
- + + + ``` -### Details -`core/details` has highly specific `save()` output constraints that will cause validation errors if guessed: -- Boolean HTML attributes must be used (`open`, not `open=""`). -- Inline styles are minified with no spaces after colons (e.g., `border-width:1px;padding-top:2px`). -- The `
` tag is the wrapper; the `` tag is a direct child with no wrapper. -- The closing `
` tag must immediately follow the last inner block's closing comment with **no newline**. +### Button with colour presets, padding, border-radius +`backgroundColor` and `textColor` preset slugs produce `has-{slug}-background-color` and `has-{slug}-color` classes on the `` tag. `style.border.radius`, `style.spacing.padding.*` are serialised into the `` tag's inline style. + +Style property order on the `` tag (Gutenberg serialises in this sequence): +`border-radius` → `padding-top/right/bottom/left` → `font-size` (if set) → `font-weight` (if set) ```html - -
Summary text -

Hidden content

-
- + +
+ ``` -### Group with inner paragraph +### Button with inline font-size (raw value, not preset) +When `style.typography.fontSize` is a raw value (e.g. `"1.125rem"`), **not** a preset slug, two additional things are required: +1. `has-custom-font-size` class on the `` tag (after `has-background`, before `wp-element-button`) +2. `font-size` appears in the inline style **before** `font-weight` + +```html + + + +``` + +> ⚠️ Missing `has-custom-font-size` or wrong style order both pass Tier 1 and look identical in the browser but fail Tier 3. + +--- + +## Group block signatures + +### Basic group with constrained layout ```html
@@ -103,7 +132,35 @@ These are exact representations of what WordPress would write to the database. C ``` -### Columns (two equal columns) +### Group with background colour +`style.color.background` using a preset reference produces `has-background` class; the actual colour is applied via `background-color` in the inline style using the CSS custom property reference. + +```html + +
+

Content.

+
+ +``` + +### Group with background image overlay +`style.background.backgroundImage` stores the image metadata in the comment JSON. The group div gets `has-background` but the background-image CSS is **not** in the inline style — it is applied via a generated CSS custom property (`--wp--style--background-image`). The `background-color` and `padding-*` values are still serialised as inline styles as normal. + +```html + +
+

Content.

+
+ +``` + +> Note: The background-image URL does not appear in the div's inline style. WordPress outputs it separately via a `