# Divi 5 Lessons Learned

Everything we know about building, deploying, and debugging Divi 5 sites on the Kaburu infrastructure (Hetzner/CyberPanel/LiteSpeed). Compiled from real builds, failures, and fixes.

## Version & Environment

### Active Divi 5 Sites

Site Divi Version DiviOps Agent DiviFlash MCP Server
——————-————–———–————
llmtest.kaburu.co 5.8.1 1.5.5 1.4.14 `diviops-llmtest`
kaburu.co 5.8.0 1.5.5 5.2.0 `diviops`
l8.kaburu.co.uk 5.8.1 1.5.5 5.2.0 `diviops-l8`
thechippyvan.co.uk 5.8.1 1.5.5 `diviops-chippy`

### Key Fact Divi 5 includes the Visual Builder in the theme itself. There is no separate “Divi Builder” or “ET Builder” plugin. If Divi isn't rendering, check the theme is active (`wp theme list`) — not a missing plugin.

## DiviOps MCP — Primary Build Tool

### Architecture

``` Hermes/Z840 ◄──► DiviOps MCP Server (npx/@diviops/mcp-server v1.5.33)

                    │
          WordPress REST API (/wp-json/diviops/v1/)
                    │
          diviops-agent plugin (v1.5.5, free, MIT license)
                    │
          Divi 5 blocks written to post_content
          (VB-editable, frontend-renderable)

```

### Tool Inventory: 85 Free Tools

All 85 execution tools are free. DiviOps Pro is not needed — it sells knowledge (curated docs), not capability. We can query module schemas ourselves via `diviops_schema_get_module`.

Page Authoring: `page_create`, `page_get`, `page_get_layout`, `page_list`, `page_update_content`, `page_update_meta`, `page_update_status`, `section_append`, `section_replace`, `section_remove`

Module Operations: `module_get`, `module_update` (dot notation), `module_clone`, `module_move`, `module_lock`/`unlock`

Design System: `global_color_*`, `global_font_*`, `variable_*`, `variable_create_fluid_system`, `preset_*`

Validation: `validate_blocks`, `render_preview`, `meta_flush_cache`

Schema: `schema_list_modules` (divi/* only), `schema_get_module` (full attribute tree)

Theme Builder: `tb_template_create`, `tb_template_list`, `tb_layout_get`/`update`, `tb_layout_block_insert`

Templates: `template_list`, `template_get` — pre-verified patterns: `hero-centered`, `features-blurbs`, `cta-gradient`, `cards-flex`

Other: `library_*`, `canvas_*`, `menu_*`, `meta_find_icon`, `meta_wp_cli` (safe allowlist)

### Setting Up a New MCP Server

1. Install plugin: Copy `diviops-agent/` from existing site on same server 2. Create app password: `wp user application-password create kaburu 'diviops-mcp' –porcelain` 3. Register in Hermes config using Python YAML manipulation (NOT `hermes mcp add` — it mangles comma-separated args) 4. Restart session (`/reset`) — no live reload exists

Full procedure in the `divi-5-builder` skill at `/home/kaburu/.hermes/skills/infrastructure/divi-5-builder/SKILL.md`.

## Critical Rules

### 1. The CREATE vs MODIFY Rule (THE MOST IMPORTANT RULE)

This is the single most important fact about Divi 5 programmatic work:

MODIFY existing VB-created blocks via REST API = WORKS ✅ The Visual Builder reads updated block JSON on next open and renders changes correctly. This is proven for: - Batch text updates across many pages - Style migration (spacing, colors, typography) - Content replacement (swap placeholder text with real copy)

CREATE new blocks via REST API = INVISIBLE in VB ❌ Content created via REST API, direct DB writes, or any backend method renders on the frontend. But the VB Layers panel shows only a single empty “section.” The client cannot select, move, or edit programmatically-created blocks.

Why: Divi 5 blocks do NOT register in Divi's internal React state. The VB loads content into its own state management layer, and blocks it did not create are invisible to that layer.

Practical consequence: We build pages via the DiviOps MCP (which writes directly to post_content), and those pages render perfectly on the frontend — Steve just can't edit them visually. For pages that will be built once and never manually adjusted, this is fine. For pages that need ongoing client edits, create structure in VB first, then REST API for bulk attribute changes.

### 2. Use Python json.dumps() for Block Generation

NEVER hand-write Divi 5 block JSON. Deeply nested decoration paths like `headingFont.h1.font.desktop/tablet/phone` cause brace mismatches that silently break rendering. Use Python's `json.dumps()` to generate all block markup.

Template script: `scripts/gen-divi-page.py` (helper functions for all block types, decoration presets, validation, and push instructions).

### 3. builderVersion

Use `“5.0.3”` (block spec version), NOT the Divi theme version (e.g. “5.8.1”).

### 4. innerContent Path

Must be at `block.content.innerContent.desktop.value`. Wrong path → empty modules.

### 5. Unicode Escapes

`\u003c` in JSON → stores as literal in DB → renders as `<` on frontend. Don't let JSON.stringify or json.dumps decode them to actual characters before storage.

### 6. FastPixel → VB 500s

If `fastpixel-website-accelerator` is active, the Visual Builder returns HTTP 500 on every page. Deactivate it before any VB work. (Already removed from kaburu.co.)

### 7. Postmeta for Divi Pages

DiviOps sets these automatically, but verify if pages don't render: - `_et_pb_use_builder` = `on` - `_et_pb_page_layout` = `et_full_width_page`

## Page Building Workflow

### Proven Workflow (L8 Water Hygiene, 62 blocks)

``` 1. Build Python script with helper functions (text_block, button_block, etc.) 2. Generate blocks using json.dumps() → guarantees correct JSON 3. Write to file on Z840 (e.g. /tmp/l8water-content.html) 4. SCP file to Hetzner (via Tailscale IP 100.112.54.2) 5. wp post update PAGE_ID /tmp/file.html –allow-root 6. wp post meta update PAGE_ID _et_pb_use_builder on –allow-root 7. wp post meta update PAGE_ID _et_pb_page_layout et_full_width_page –allow-root 8. wp cache flush –allow-root 9. Verify with curl -sL https://site/page | grep -o 'expected text' ```

### Why SCP + WP-CLI (Not MCP for Large Pages)

For pages with >7 blocks, the MCP transport layer can double-escape backslashes: `\u003c` becomes `\\u003c` in the DB. Content is stored but renders empty. Bypass MCP entirely for complex pages — write the block markup to a file, SCP to Hetzner, push via `wp post update`.

### Visual QA (After Build)

```bash # Single screenshot + analysis python3 /home/kaburu/scripts/vision-check.py https://site.com/page

# Full-page scroll review (3 viewport scrolls) python3 /home/kaburu/scripts/scroll-review.py https://site.com/page –num 3 ```

Uses local minicpm-v:8b vision model. SKIP for auth-walled pages (vision model sees login page).

## DiviFlash Integration

### Module Arsenal

Source Block namespace Count Key Modules
——–—————-——-————-
Native Divi 5 `divi/*` 80+ text, button, blurb, image, accordion, slider, hero
DiviFlash v5.2.0 `difl/*` 107 faq, flip-box, content-carousel, testimonial-carousel, pricing-table, bento-grid, lottie, svg-animator, typing-text, tilt-card, timeline, dual-button, image-hover, scroll-image
Contact Form Extender `difl/cfseven` 1 Extended contact form (file upload, signature, date picker, rating)

Full inventory: `/home/kaburu/.hermes/skills/infrastructure/divi-5-builder/references/diviflash-modules.md`

### Usage Pattern

DiviOps MCP only registers `divi/*` modules in its schema tools. To use `difl/*`: 1. Read the module schema from server: `cat wp-content/plugins/diviflash/Builder/Server/modules-json/<Module>/module.json` 2. Write blocks as `<!– wp:difl/faq {“module”:{…}} –>…<!– /wp:difl/faq –>` 3. Save via `diviops_page_create` or `diviops_page_update_content` 4. Preview via `diviops_render_preview` (may not render DiviFlash in preview, but WILL on frontend) 5. Verify with `curl -sL https://site.com/?page_id=N` on server

### DiviFlash on llmtest

llmtest.kaburu.co has DiviFlash 1.4.14 (older version). kaburu.co and l8.kaburu.co.uk have 5.2.0. The older version may lack modules available in 5.2.0. If Steve needs the full module set on llmtest, copy the plugin from kaburu.co.

## Server Configuration (LiteSpeed/CyberPanel)

### The php.ini Trap (MOST IMPORTANT SERVER GOTCHA)

LiteSpeed IGNORES `.user.ini` and vhost `phpIniOverride` for PHP settings. It reads ONLY from the system php.ini:

``` /usr/local/lsws/lsphp83/etc/php/8.3/litespeed/php.ini ```

Symptoms: - Plugin uploads fail with “exceeds upload_max_filesize directive” - WP-CLI shows 2M/8M even though .user.ini says 256M

Fix: ```bash sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 256M/' /usr/local/lsws/lsphp83/etc/php/8.3/litespeed/php.ini sed -i 's/post_max_size = 8M/post_max_size = 256M/' /usr/local/lsws/lsphp83/etc/php/8.3/litespeed/php.ini sed -i 's/memory_limit = 128M/memory_limit = 256M/' /usr/local/lsws/lsphp83/etc/php/8.3/litespeed/php.ini systemctl reload lsws ```

### Theme/Plugin Permissions After Copy

When copying themes with `cp -r`, source permissions (750/640) break LiteSpeed: ```bash find /path/to/theme -type d -exec chmod 755 {} \; find /path/to/theme -type f -exec chmod 644 {} \; ```

### wp-json Routing

If MCP connections fail with “WordPress API non-JSON body (200)” — the request is returning HTML instead of JSON. LiteSpeed doesn't always route `wp-json` paths correctly. Workaround: add rewrite rule to vhost config.

### Cloudflare Blocks REST API from Z840

Direct REST API calls from kaburuaibox to WordPress sites return HTTP 403 (Cloudflare error 1010). The Hetzner relay pattern (base64 encode → SSH stdin pipe → decode on Hetzner → curl) bypasses this.

### WP-Cron: Use wget, NOT wp cron event run

Running `wp cron event run –allow-root` creates root-owned cache files. Use: ```bash */15 * * * * wget -q -O - https://<domain>/wp-cron.php?doing_wp_cron ```

## Image Generation Pipeline

### Primary: ComfyUI (Local)

- Endpoint: `http://localhost:8188` - Model: Juggernaut XL v9 (6.7GB, photorealistic SDXL) - Status: Currently stopped (2026-07-04). Both 3060s run Ollama (minicpm-v + Mistral Nemo). - Full workflow: POST prompt → poll history → download image → Hetzner relay upload - Script: `scripts/comfyui-generate.py`

### Fallback: OpenAI DALL-E 3

`OPENAI_API_KEY` in `~/.hermes/.env`. Used when ComfyUI is down.

### Upload Architecture (Hetzner Relay)

``` Z840: base64 encode image

→ SSH stdin pipe to Hetzner (Tailscale: [email protected])
  → Hetzner decodes + curl to WP REST API
    → Returns attachment_id + URL

```

Critical: Must use stdin pipe, NOT command-line args. Images >~100KB cause “Argument list too long” error via cmdline args.

### WP App Passwords

- l8.kaburu.co.uk: `kaburu:khajJiBbhnmUM9kVZW7JWHWM` (name: divi-bot-pipeline) - kaburu.co: `kaburu:qPAxzp9fegmvRE3yKRDO6LNq`

Lifecycle: Old app passwords can start returning 401. Create fresh ones if uploads fail: ```bash wp user application-password create kaburu “divi-bot-pipeline” ```

### No ImageMagick/GD on Hetzner

`wp media import` fails with “No support for generating images.” Workaround: host images on an existing WP site and reference cross-origin URL, or install `php-imagick` on Hetzner.

## Page Building Reference

### Block Type → Decoration Mapping

HTML/CSS Divi Attribute Path
———-——————-
`background: linear-gradient(…)` `module.decoration.background.desktop.value.gradient`
`padding: 100px 0` `module.decoration.spacing.desktop.value.padding`
`font-size: 68px; color: #fff` `content.decoration.headingFont.h1.font.desktop.value`
`border-radius: 10px` `module.decoration.border.desktop.value.radius`

### HTML → Divi Section Mapping

Visual Element Divi/DiviFlash Module
————————————-
Hero/banner `divi/section` + `divi/row` + `divi/text` + `divi/button`
Feature cards `divi/row` (3 cols) + `divi/blurb` per column
Testimonials `divi/testimonial` or `difl/testimonialcarousel`
FAQ `difl/faq` (richer than native accordion)
Pricing `difl/pricingtable` or `divi/pricing-tables`
Contact form `difl/cfseven` (Contact Form Extender)
Image galleries `difl/justifiedgallery`, `difl/packerygallery`
Animations `difl/lottie`, `difl/svganimator`, `difl/typing-text`

## Debugging Empty Text Modules

When text modules render as empty `<div class=“et_pb_text_inner”></div>`:

1. Are buttons rendering? If yes → JSON parse failure on text blocks. If no → block parser can't find nested blocks. 2. Extract JSON from DB, run json_decode → fails? Count braces → mismatch? → Hand-written JSON bug. Use json.dumps(). 3. Braces match but still fails? → Check for double-escaped backslashes (`\\u003c` instead of `\u003c`). MCP transport issue → write to file, SCP, wp post update. 4. JSON decodes OK but content empty? → innerContent at wrong path. Must be at `content.innerContent.desktop.value`. 5. `headingFont.h1.font` nesting trap — `h1` contains `font` which contains `desktop/tablet/phone`. Close `font` with `}}}` before `bodyFont`.

Full diagnostic flowchart in the skill reference: `references/debugging-empty-modules.md`.

## SEO Automation

### RankMath - REST API: `/wp-json/rankmath/v1/` — full meta, schema, search intent, link analysis - Script: `scripts/rankmath-seo.py` (set-meta, keyword-research, set-schema, orphans, robots, full-seo) - No WP-CLI commands — REST API is the only programmatic path

### RankWatch - API: `https://apiv2.rankwatch.com/` — ranking tracking, keyword management - Script: `scripts/rankwatch.py` (projects, rankings, add-keyword, report) - Auth: Basic HTTP auth (token:password). NOTE: all API paths require trailing slash.

### Markdown for Agents - Plugin: `markdown-for-agents-and-statistics` v1.5.0 - Active on: rainbowvapes.co.uk, l8.kaburu.co.uk, myretonmarquees.co.uk, jafricasafari.com - Serves lightweight markdown to AI crawlers instead of rendered Divi pages

## Domain Switching (Divi Sites)

When switching a Divi site to a new domain on Hetzner:

1. Cloudflare DNS — Update A record, proxied. Update www CNAME. 2. LiteSpeed vhost — Create vhost.conf for new domain pointing to same docroot. 3. httpd_config.conf — Add `virtualHost` block + `map` entries in both HTTP and HTTPS listeners. 4. CyberPanel DB — INSERT into `websiteFunctions_websites`. ⚠️ `ssl` is reserved word — must backtick-escape. 5. WordPress — `wp option update siteurl` + `wp option update home` + `wp search-replace`. Skip guid column. 6. FastPixel — Deactivate during switch, reactivate after. Old cached 301s persist otherwise. 7. Child theme — `sed -i` hardcoded domain references. 8. MainWP — Update URL in DB: `UPDATE wpfm_mainwp_wp SET url=“…” WHERE id=N`. 9. FluentSMTP — ⚠️ If half-configured, `wp user update` throws PHP Fatal. Use `wp eval` with `$wpdb→update()` instead.

## Complete Pitfall Table

# Pitfall Fix
————–
0 Divi theme bundles the builder — no separate plugin Just activate the Divi theme
1 `innerContent` at wrong JSON path → empty modules Must be at `block.content.innerContent.desktop.value`
2 `builderVersion` set to theme version (5.8.1) Use `“5.0.3”` (block spec version)
3 FastPixel active → VB returns 500 Deactivate `fastpixel-website-accelerator`
4 Page created but frontend blank Check `_et_pb_use_builder=on` postmeta, flush cache
5 DiviFlash modules don't show in DiviOps schema Expected — `difl/*` not registered. Read module.json directly
6 `render_preview` shows content but frontend doesn't Flush cache: `diviops_meta_flush_cache(post_id=N)`
7 `flush_cache` wrong parameter format Pass `post_id` as integer, not `page_id`
8 Unicode escapes decoded before storage Keep `\u003c` as literal string
9 Hand-written JSON → mismatched braces → empty modules Use Python `json.dumps()` — never hand-write
10 MCP double-escapes backslashes on >7 blocks Write to file, SCP, `wp post update`
11 `headingFont.h1.font` nesting trap Close `font` with `}}}` before `bodyFont`
12 `wp media import` fails — no ImageMagick/GD Install `php-imagick` or use cross-origin URL
13 New MCP servers need session restart `/reset` — no live reload
14 LiteSpeed ignores vhost phpIniOverride and .user.ini Edit system php.ini directly
15 Theme copy inherits 750/640 perms → 403 `find … -exec chmod 755/644`
16 LiteSpeed .htaccess ignored → permalinks 404 Embed rewrite rules in vhost config

## Never Do This

- ❌ Never create new blocks via REST API and expect them to be VB-editable. They render on frontend but are invisible in the Visual Builder. - ❌ Never hand-write Divi 5 block JSON. Use Python `json.dumps()`. A single missing `}` breaks all text modules. - ❌ Never SSH as root for WordPress operations. Creates root-owned files (broke 23 sites, 11,525 files). Use MCP/CyberPanel tools or `sudo -u <siteuser>`. - ❌ Never use `wp cron event run –allow-root` for WP-Cron. Creates root-owned cache files. Use wget crons. - ❌ Never set builderVersion to the theme version. Use `“5.0.3”` (block spec). - ❌ Never embed base64 images in SSH command-line args. Use stdin pipe for images >~100KB. - ❌ Never call WordPress REST API in parallel from the same session. Requests hang indefinitely. Chain sequentially. - ❌ Never regex-extract block JSON. Use a brace-depth parser (`–>` can appear inside JSON strings).

## Connected Sites & MCP Servers

MCP server name Site Divi DiviOps DiviFlash
—————–——————————–
`diviops` kaburu.co 5.8.0 1.5.5 5.2.0
`diviops-l8` l8.kaburu.co.uk 5.8.1 1.5.5 5.2.0
`diviops-chippy` thechippyvan.co.uk 5.8.1 1.5.5
`diviops-llmtest` llmtest.kaburu.co 5.8.1 1.5.5 1.4.14

## Skill & Script Locations

All tooling lives in the `divi-5-builder` skill at: ``` /home/kaburu/.hermes/skills/infrastructure/divi-5-builder/ ├── SKILL.md ← Main reference ├── scripts/ │ ├── gen-divi-page.py ← Page generator (USE THIS) │ ├── comfyui-generate.py ← Image generation CLI │ ├── rankmath-seo.py ← SEO automation CLI │ ├── rankwatch.py ← RankWatch API CLI │ ├── page-generator-template.py ← L8 real-world example │ └── rest-bulk-modify.py ← REST API bulk modifier └── references/

  ├── divi5-create-vs-modify.md         ← The CREATE vs MODIFY rule
  ├── diviflash-modules.md              ← 107 module inventory
  ├── debugging-empty-modules.md        ← Diagnostic flowchart
  ├── litespeed-gotchas.md              ← Server-level gotchas
  ├── litespeed-wpjson-routing.md       ← wp-json fix
  ├── site-migration-and-cloning.md     ← Cloning to build domain
  ├── l8water-deployment.md             ← Production deployment log
  ├── image-pipeline.md                 ← ComfyUI/DALL-E workflow
  ├── rankmath-seo-api.md               ← Full REST API reference
  ├── rankwatch-api.md                  ← Full API reference
  ├── diviops-pro-analysis.md           ← Why Pro isn't needed
  ├── known-limitations.md              ← 18 rules quick reference
  ├── css-patterns.md                   ← CSS specificity, Free-Form CSS
  ├── rest-api-patterns.md              ← Brace-depth parser, encoding
  └── web-mcp-integration.md            ← Browser MCP (replaced Playwright)

```

## External References

- 16wells Divi 5 Docs: https://16wells.github.io/divi-docs/ - DiviOps GitHub: https://github.com/oaris-dev/diviops - DiviOps npm: `@diviops/mcp-server` - OpenLiteSpeed + Wordfence: https://openlitespeed.org/kb/enable-wordfence-on-openlitespeed/