Skip to content
Closed
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 226 additions & 0 deletions apps/cli/ai/plugin/skills/blockify/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
---
name: blockify
description: Convert all core/html blocks in a WordPress site to native Gutenberg blocks. Run this after building a site (Phase 2) or on any existing site that uses custom HTML blocks.
user-invokable: true
---

# Block Conversion (Blockify)

Convert all `core/html` blocks in a site's pages, posts, and template parts to native Gutenberg blocks. The CSS stays untouched — the visual output must remain identical.

**The goal is FAITHFUL CONVERSION.** Convert every `core/html` block that has a native block equivalent. Do not make editorial decisions about what to skip. Do not "improve" or simplify the structure. Reproduce the exact same content using native blocks.

## How to Run

### Step 1 — Read back all content

Retrieve every piece of block content. You MUST read ALL of these before proceeding:
- Page/post content: `wp_cli post list --post_type=page,post --fields=ID,post_title` then `wp_cli post get <id> --field=post_content` for each
- Template part files: Read header.html, footer.html, and ALL other template part files from the theme's `parts/` directory

Do NOT skip template parts — they often contain navigation, hero sections, or footer content that needs conversion.

### Step 2 — Audit every core/html block

Before planning the conversion, list every `core/html` block you found across all content. For each one, state:
- Where it is (page/post name or template part file)
- What HTML it contains (the wrapper tag and a brief summary)
- What it will become: the specific native block(s), OR `core/html` (kept) with the reason

Example:
```
1. Homepage hero wrapper: <section class="hero-section"> with heading, paragraph, buttons, image, scroll indicator
→ core/group (tagName="section") with inner core/heading + core/paragraph + core/buttons + core/image + core/html (scroll indicator only)
2. Homepage script tag: <script src="iconify">
→ core/html (kept — script tag, no block equivalent)
3. Header nav logo: <div class="logo"><a>Site Name</a></div>
→ core/group with inner core/paragraph
```

This audit ensures nothing is silently skipped. Every `core/html` block must be accounted for.

### Step 3 — Plan the conversion element-by-element (using audit from Step 2)

**CRITICAL: Always decompose.** Never keep an entire section as `core/html` just because it contains some non-convertible sub-elements. Break the section apart: convert every convertible element to native blocks and isolate only the truly non-convertible elements as individual `core/html` blocks.

For example, a hero section with a heading, paragraph, buttons, image, AND a scroll-indicator animation should become: `core/group` > `core/heading` + `core/paragraph` + `core/buttons` + `core/image` + `core/html` (scroll indicator only). Do NOT keep the entire hero as `core/html`.

For each section of content, decide what converts to native blocks and what stays as `core/html`:

| HTML | Gutenberg block |
|------|----------------|
| `<section>`, `<div>`, `<header>`, `<footer>`, `<aside>` | `core/group` with appropriate `tagName` |
| `<h1>`–`<h6>` | `core/heading` with matching `level` |
| `<p>` | `core/paragraph` |
| `<a class="btn">` / CTA links | `core/buttons` + `core/button` |
| CSS grid/flex layouts with `<div>` children | `core/columns` + `core/column` |
| `<ul>` / `<ol>` | `core/list` + `core/list-item` |
| `<img>` | `core/image` |
| `<figure>` | `core/image` or `core/media-text` |
| `<blockquote>` | `core/quote` |
| `<table>` | `core/table` |
| `<hr>` | `core/separator` |
| Empty spacing `<div>` | `core/spacer` |

Keep `core/html` ONLY for individual elements with no native block equivalent:
- Inline SVGs (icons, illustrations, decorative graphics)
- `<form>` elements and interactive inputs
- `<canvas>`, `<iframe>`, `<video>`, `<audio>`
- Animation/interaction markup (marquee, custom cursor, scroll-triggered elements)
- Elements needing custom `data-*` attributes for JS interactivity
- `<script>` tags — always extract into their own separate `core/html` block, never bundled with structural content

**Things that are NOT valid reasons to keep an element as `core/html`:**
- `id` attributes — `core/group` supports `anchor` for element IDs
- Inline `<em>`, `<strong>`, `<br>`, `<a>` inside text — `core/heading` and `core/paragraph` support inline HTML
- `loading="eager"` on images — drop the attribute rather than keeping the whole section as HTML
- A single non-convertible child — decompose the section instead of skipping it entirely
- Small text-containing `<div>` or `<span>` elements (eyebrow text, labels, captions, taglines) — convert to `core/paragraph` with the appropriate `className`
- Decorative wrapper `<div>` elements — convert to `core/group` with `className`
- **Section wrappers with background images, overlays, or gradients** — these are always `core/group` with `className`. All visual effects (background-image, overlays, pseudo-elements, gradients) are handled by CSS targeting the className, not by the block markup. There is no reason to keep a `<section>` or `<div>` wrapper as `core/html` because of its visual styling.

**Be thorough.** Convert every single element that has a native block equivalent. Do not leave small or simple elements as `core/html` out of convenience. A `<div>` with text inside it is a `core/paragraph` or `core/group`, not a Custom HTML block.

All CSS classes from the original design stay in style.css — the visual output must remain identical after conversion.

### Step 4 — Write the converted content

Rewrite the full content for each page/post and template part using native Gutenberg block markup. Use the block patterns below as reference. Update posts via `wp_cli post update` and template parts via Write/Edit.

**IMPORTANT — CSS migration must be atomic replacements, not additions.** When converting elements to native blocks, the block markup changes the DOM structure (e.g., a bare `<a class="btn-gold">` becomes `<div class="wp-block-button btn-gold"><a class="wp-block-button__link wp-element-button">`). The Phase 1 CSS rules targeting the old selectors will now apply to the wrong elements, causing double borders, double backgrounds, and broken layouts.

**The rule:** Every time you write a new block-scoped CSS rule, you MUST delete the Phase 1 rule it replaces. One rule out, one rule in. Never leave both.

For example, when converting buttons:
- **Delete** the Phase 1 rule: `.btn-gold { background: gold; border: 2px solid gold; padding: 1rem 2rem; }`
- **Replace** with a block-scoped rule: `.wp-block-button.btn-gold .wp-block-button__link { background: gold; border: 2px solid gold; padding: 1rem 2rem; }`

This applies to all converted elements, not just buttons. If a Phase 1 rule targeted a bare class that is now a `className` on a block wrapper, rewrite the rule to target the correct block DOM structure.

### Step 5 — Validate block markup

Run `validate_blocks` on every piece of converted content to catch markup errors (missing attributes, invalid nesting, malformed block comments). If it flags invalid blocks, fix the markup and re-run until all blocks pass.

### Step 6 — Visual regression check

Take screenshots (desktop + mobile) and compare them against the Phase 1 screenshots you already have in context. The site must look identical to the Phase 1 result. Look specifically for these common regressions introduced by block conversion:

- **Double borders or backgrounds on buttons** — `core/button` adds `.wp-block-button__link` with default border/background that stacks on your CSS
- **Extra padding or spacing around sections** — `core/group` and `.wp-block-columns` add default padding/gap
- **Missing background colors or gradients** — elements that had inline styles may lose them when converted to blocks
- **Font size or weight changes** — block defaults may override your typography
- **Broken hover/active states** — interactive styles may target the old selectors

If any regressions are found, update `style.css` to fix the conflicts (typically by resetting default block styles for the affected classes), then re-screenshot and compare again. Iterate until the design matches Phase 1 exactly.

## Block pattern reference

### Section wrapper

Replaces `<section>`, `<div>`, `<aside>`, `<header>`, `<footer>`:
```
<!-- wp:group {"tagName":"section","className":"hero-section","layout":{"type":"default"}} -->
<section class="wp-block-group hero-section">
<!-- inner blocks go here -->
</section>
<!-- /wp:group -->
```

### Heading

Replaces `<h1>`–`<h6>`:
```
<!-- wp:heading {"level":1,"className":"hero-title"} -->
<h1 class="wp-block-heading hero-title">Your Title</h1>
<!-- /wp:heading -->
```

### Paragraph

Replaces `<p>`:
```
<!-- wp:paragraph {"className":"hero-subtitle"} -->
<p class="hero-subtitle">Your text here.</p>
<!-- /wp:paragraph -->
```

### Columns layout

Replaces CSS grid/flex with `<div>` children:
```
<!-- wp:columns {"className":"features-grid"} -->
<div class="wp-block-columns features-grid">
<!-- wp:column -->
<div class="wp-block-column">
<!-- inner blocks -->
</div>
<!-- /wp:column -->
<!-- wp:column -->
<div class="wp-block-column">
<!-- inner blocks -->
</div>
<!-- /wp:column -->
</div>
<!-- /wp:columns -->
```

### Image

Replaces `<img>`:
```
<!-- wp:image {"className":"hero-image"} -->
<figure class="wp-block-image hero-image"><img src="https://example.com/image.jpg" alt="Description"/></figure>
<!-- /wp:image -->
```

### Buttons

Replaces `<a class="btn">`:
```
<!-- wp:buttons {"className":"hero-cta"} -->
<div class="wp-block-buttons hero-cta">
<!-- wp:button {"className":"primary-btn"} -->
<div class="wp-block-button primary-btn"><a class="wp-block-button__link wp-element-button" href="#">Get Started</a></div>
<!-- /wp:button -->
</div>
<!-- /wp:buttons -->
```

### List

Replaces `<ul>` / `<ol>`:
```
<!-- wp:list {"className":"feature-list"} -->
<ul class="feature-list">
<!-- wp:list-item -->
<li>First item</li>
<!-- /wp:list-item -->
<!-- wp:list-item -->
<li>Second item</li>
<!-- /wp:list-item -->
</ul>
<!-- /wp:list -->
```

### Separator

Replaces `<hr>`:
```
<!-- wp:separator {"className":"section-divider"} -->
<hr class="wp-block-separator section-divider"/>
<!-- /wp:separator -->
```

## Nesting blocks

Sections are built by nesting blocks inside `core/group`. All visual styling (grid layouts, spacing, colors, backgrounds, animations) goes in `style.css` targeting the `className`. The block structure is for editability; the CSS is for aesthetics.

## Additional rules

- Never use `core/html` to wrap text content, headings, layout sections, or lists.
- No decorative HTML comments (e.g. `<!-- Hero Section -->`, `<!-- Features -->`). Only block delimiter comments are allowed.
- No custom class names on inner DOM elements — only on the outermost block wrapper via the `className` attribute.
- No inline `style` or `style` block attributes for styling. Use `className` + `style.css` instead.
- Use `core/spacer` for empty spacing divs, not `core/group`.
- No emojis anywhere in generated content.
- Adding `data-*` attributes does NOT make a block acceptable — use `className` on `core/group` blocks instead.
27 changes: 15 additions & 12 deletions apps/cli/ai/system-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,7 @@ function buildLocalIntro(): string {
return `${ AGENT_IDENTITY } You manage and modify local WordPress sites using your Studio tools and generate content for these sites.

IMPORTANT: You MUST use your mcp__studio__ tools to manage WordPress sites. Never create, start, or stop sites using Bash commands, shell scripts, or manual file operations. Never run \`wp\` commands via Bash — always use the wp_cli tool instead. The Studio tools handle all server management, database setup, and WordPress provisioning automatically.
IMPORTANT: For any generated content for the site, these three principles are mandatory:

- Gorgeous design: More details on the guidelines below.
- No HTML blocks and raw HTML: Check the block content guidelines below.
- No invalid block: Use the validate_blocks everytime to ensure that the blocks are 100% valid.
IMPORTANT: Site building has two phases. In Phase 1 (Design), focus exclusively on creating a gorgeous, visually striking site — write HTML/CSS freely without worrying about block syntax. In Phase 2 (Block Conversion), convert all content to native Gutenberg blocks and validate. Both phases are mandatory; never skip Phase 2.

## Workflow

Expand All @@ -112,11 +108,18 @@ For any request that involves a WordPress site, you MUST first determine which s
Then continue with:

1. **Get site details**: Use site_info to get the site path, URL, and credentials.
2. **Plan the design**: Before writing any code, review the site spec (from the site-spec skill) and the Design Guidelines below to plan the visual direction — layout, colors, typography, spacing.
3. **Write theme/plugin files**: Use Write and Edit to create files under the site's wp-content/themes/ or wp-content/plugins/ directory.
4. **Configure WordPress**: Use wp_cli to activate themes, install plugins, manage options, create posts and pages, edit and import content. The site must be running. Note: post content passed via \`wp post create\` or \`wp post update --post_content=...\` need to be pre-validated for editability and also validated using validate_blocks tool and adhere to the block content guidelines above as well. The \`wp_cli\` tool takes literal arguments, not shell commands: never use shell substitution or shell syntax such as \`$(cat file)\`, backticks, pipes, redirection, environment variables, or host temp-file paths to provide post content. Pass the literal content directly in \`--post_content=...\`, make \`--post_content\` the final argument in the command, and Studio will rewrite large content to a virtual temp file automatically.
5. **Check the misuse of HTML blocks**: Verify if HTML blocks were used as sections or not. If they were, convert them to regular core blocks and run block validation again.
6. **Check the result**: Use take_screenshot to capture the site's landing page on desktop and mobile and verify the design visually on both viewports, check for wrong spacing, alignment, colors, contrast, borders, hover styles and other visual issues. Fix any issues found. Pay particular attention to the navigation menu and the CTA buttons. The design needs to match your original expectations.

### Phase 1 — Design

2. **Plan the visual design**: Before writing any code, review the site spec (from the site-spec skill) and the Design Guidelines below to plan the full visual direction — layout, typography, color palette, spacing, motion, backgrounds. Think about what makes this design UNFORGETTABLE. Do NOT think about Gutenberg block syntax during this phase.
3. **Build the site**: Write theme files (style.css, functions.php, templates, template parts) and create pages/posts with wp_cli. During this phase, write content as clean HTML — you may use \`core/html\` blocks or raw HTML freely. Focus entirely on getting the visuals right. All CSS goes in style.css as usual. The \`wp_cli\` tool takes literal arguments, not shell commands: never use shell substitution or shell syntax such as \`$(cat file)\`, backticks, pipes, redirection, environment variables, or host temp-file paths to provide post content. Pass the literal content directly in \`--post_content=...\`, make \`--post_content\` the final argument in the command, and Studio will rewrite large content to a virtual temp file automatically.
4. **Visual review**: Use take_screenshot to capture the site's landing page on desktop and mobile and verify the design visually on both viewports. Check for wrong spacing, alignment, colors, contrast, borders, hover styles, and other visual issues. Fix any issues found. Pay particular attention to the navigation menu and the CTA buttons. Iterate until the design is excellent.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wonder if this is needed. Right now the agent is really slow, it takes too much screenshots all the time. I wonder if one "validation" phase at the end is better perf wise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have measured the site creation time of the current changes (as at commit ea7817c) and they are much longer than the version on trunk (~ 18 minutes vs ~ 8 minutes on trunk). If on trunk we apply the blockify skill, it conversts successfully the blocks in ~ 5 minutes so in total it would be ~ 13:30 minutes. Given that, I have reverted the changes to be as similar as trunk as possible with the latest step being the blockify and the validations and it seems to improve, now it takes ~ 14 minutes.

I am going to continue doing some testing but the latest changes improve the multiple loops

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have refined the system prompt to be:

  • Similar to trunk version
  • Added a step to transform html to core blocks
  • Validate as the last step

The time it has taken in some of my tests is ~ 12 minutes, which is longer than trunk, which takes ~ 8 minutes, but we are adding a step.

This is ready for another 👀


### Phase 2 — Block Conversion

5. **Convert to native blocks**: Run the \`blockify\` skill to convert all \`core/html\` blocks to native Gutenberg blocks, validate the markup, and verify the design is preserved.
6. **Validate block markup**: Run \`validate_blocks\` on every piece of converted content to catch markup errors (missing attributes, invalid nesting, malformed block comments). If it flags invalid blocks, fix the markup and re-run until all blocks pass.
7. **Final visual check**: Take screenshots again (desktop + mobile) to confirm the block conversion did not break the design. Fix any visual regressions — the site must look identical to the Phase 1 result.

## Available Studio Tools (prefixed with mcp__studio__)

Expand All @@ -131,13 +134,13 @@ Then continue with:
- preview_update: Update an existing hosted WordPress.com preview from a local site; this can take a few minutes, so tell the user to wait
- preview_delete: Delete a hosted WordPress.com preview by hostname
- wp_cli: Run WP-CLI commands on a running site
- validate_blocks: Validate block content for correctness on a running site (runs each block through its save() function in a real browser). Requires a site name or path. Call after every file write/edit that contains block content.
- validate_blocks: Validates block markup on a running site by running each block through its save() function in a real browser. Catches invalid attributes, malformed nesting, and markup mismatches. Used by the \`blockify\` skill during block conversion.
- take_screenshot: Take a full-page screenshot of a URL (supports desktop and mobile viewports). Use this to visually check the site after building it.
- audit_performance: Measure frontend performance metrics (TTFB, FCP, LCP, CLS, page weight, DOM size, JS/CSS/image/font asset breakdown) for a running site. Use this to identify performance bottlenecks and guide optimization.

## General rules

- Design quality and visual ambition are not in conflict with using core blocks. Custom CSS targeting block classNames can achieve any visual design. The block structure is for editability; the CSS is for aesthetics.
- Design quality comes first. During Phase 1, write the best HTML/CSS you can. During Phase 2, the block structure is for editability; the CSS you wrote in Phase 1 stays for aesthetics. Custom CSS targeting block classNames achieves any visual design.
- Do NOT modify WordPress core files. Only work within wp-content/.
- Before running wp_cli, ensure the site is running (site_start if needed).
- When building themes, always build block themes (NO CLASSIC THEMES).
Expand Down
4 changes: 3 additions & 1 deletion apps/cli/ai/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,8 @@ const runWpCliTool = tool(
const validateBlocksTool = tool(
'validate_blocks',
"Validates WordPress block content by running each block through its save() function in the site's block editor (real browser). " +
'The site must be running. Returns per-block validation results with expected HTML for invalid blocks.',
'Catches invalid attributes, malformed nesting, and markup mismatches. ' +
'The site must be running. Returns per-block validation results.',
{
nameOrPath: z
.string()
Expand Down Expand Up @@ -571,6 +572,7 @@ const validateBlocksTool = tool(

const site = await resolveSite( args.nameOrPath );
const siteUrl = getSiteUrl( site );

const report = await validateBlocks( blockContent, siteUrl );

if ( report.error ) {
Expand Down
Loading