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..118550b --- /dev/null +++ b/skills/wp-block-post-content/SKILL.md @@ -0,0 +1,111 @@ +--- +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`). +- 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 — 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. + +> ⚠️ **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-file` to perform a PHP round-trip structural validation. Write a script to `/wordpress/wp-content/uploads/validate.php` that calls `parse_blocks()` → `serialize_blocks()` and confirms the round-trip returns `TRUE` with `0 freeform blocks`. A successful round-trip catches delimiter mismatches but **cannot** catch JS `save()` contract violations. +- **[TIER 2] Node.js Sandbox:** Run `node scripts/validate-markup.mjs` against your generated code. +- **[TIER 3] Chrome DevTools MCP:** Navigate to the post editor, then run the verification snippet from `references/wp-block-validation.md`. This is the ONLY automated way to verify the JS `save()` contract. `invalidCount` must equal `0` before the task is complete. +- **[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`. + +## 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. + +## 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..15a8888 --- /dev/null +++ b/skills/wp-block-post-content/references/core-block-markup-reference.md @@ -0,0 +1,318 @@ +# Core Block Markup Reference +Exact `save()` output signatures for commonly used WordPress core blocks. +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]. + +--- + +## Table of contents +- Text blocks: paragraph, heading, list, preformatted +- 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) + +--- + +## "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 block signatures + +### Basic image (with ID and size) +```html + +
Alt text
+ +``` + +### Image with border-radius +`style.border.radius` is serialised as an inline style on the `
` tag, not the ``. +```html + +
+ +``` + +### 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 + +
+ +``` + +> ⚠️ 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 + +
+ +``` + +--- + +## Button block signatures + +### Basic button +```html + + + +``` + +### 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 + + + +``` + +### 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 + +
+

Content inside a group.

+
+ +``` + +### 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 `