---
name: block-development
description: Block Builder domain contract — Route 1 scaffolder, lolacore-blocks runtime, tool routing, state machine.
domain: block_builder
type: system
version: 1.0.1
min_lolacore_version: 2.0.0
ability_prefixes:
  - create_block
  - update_block_code
  - list_built_blocks
  - get_built_block
  - get_installed_block
  - test_block
  - install_block
  - uninstall_block
  - delete_built_block
triggers:
  keywords:
    - block builder
    - built block
    - create block
    - gutenberg block
    - custom block
    - lolacore-blocks
    - bloque personalizado
    - crear bloque
    - bloques creados
    - constructor de bloques
    - bloque gutenberg
---

## DOMAIN

`block_builder` — fabricate Gutenberg blocks for the block inserter via structured spec → deterministic scaffolder → library → Playground gate → `lolacore-blocks/` runtime.

## ROUTING

Apply this playbook when `user_intent` = custom Gutenberg block, block inserter, or any `ability_prefix` in frontmatter.

| intent signal | tool |
|---|---|
| create / scaffold block | `create_block` + `spec` |
| iterate block | `update_block_code` + `spec` or MCP literal files-map |
| list / read library | `list_built_blocks`, `get_built_block` |
| compare library vs disk | `get_installed_block` |
| preview isolated | `test_block` |
| write runtime to site | `install_block` (after attestation) |
| remove runtime only | `uninstall_block` |
| trash library entry | `delete_built_block` |

`disk_root` = `wp-content/lolacore-blocks/{slug}/`. Registration = `BlockRegistry` → `register_block_type` on `init` → inserter. No Plugins screen. No `activate_plugin`.

`cross_domain_note`: editor-block requests stay in `block_builder` tools and `lolacore-blocks/` paths. Site-wide PHP plugin work uses a different ability namespace — out of scope for this playbook.

Custom blocks ≠ block patterns, template parts, synced patterns, FSE template writes (`fse-anatomy` addon).

## STATE_MACHINE

```
library_only ──install_block──► installed_on_disk ──BlockRegistry──► inserter_visible
     │                                │
     │ uninstall_block                │
     ▼                                ▼
library_only ◄──────────────── disk_removed (library entry retained)

delete_built_block → library trashed (disk copy unaffected unless uninstalled first)
```

Three independent booleans: `in_library`, `on_disk`, `in_inserter`. Report status using these; do not collapse to "live" without naming which.

## SCAFFOLD_SPEC (chat → create_block)

Required: `title`, `slug` (kebab-case, starts with letter).

Optional defaults: `description`→title, `category`→`widgets`, `icon`→`info`, `dynamic`→false, `inner_blocks`→false, `attributes`→{}, `supports`→`{"html":false}`.

`attributes.{name}.type` ∈ `string|number|boolean|array|object`; optional `default`.

`dynamic:true` → add `render.php`, set `"render":"file:./render.php"`, omit `save()` in `edit.js`.

`inner_blocks:true` → `edit.js` uses `InnerBlocks` + `InnerBlocks.Content`.

Chat path: spec only. MCP/REST path: literal files-map when `__lc_source !== 'chat'`.

## FILE_LAYOUT (Route 1, v1)

Always emit: `block.json`, `edit.js`, `edit.asset.php`, `style.css`.

`block.json`: `apiVersion` 3, `name` = `lolacore/{slug}`, `editorScript` = `file:./edit.js`, `style` = `file:./style.css`.

`edit.asset.php` deps (required): `wp-blocks`, `wp-element`, `wp-block-editor`.

`edit.js`: `wp.blocks.registerBlockType` via global `wp.*`; no ES module imports; no npm build.

`render.php` (dynamic only): `get_block_wrapper_attributes()`; prefix `if (!defined('ABSPATH')) { exit; }`.

## LIBRARY

CPT `lolacore_block`. Files meta `_lc_bb_files`. `create_block` / `update_block_code` mutate library only.

Resolve `post_id` / `slug` from `list_built_blocks` or `get_built_block` — do not invent IDs.

## GATES

Order: static safety → `test_block` (Playground) → user attestation → `install_block`.

If attestation missing for current version, surface Playground before install.

Registry collision (`lolacore/{slug}` already registered): fail install → new slug or `update_block_code`.

## CONSTRAINTS

- `block.json` `name` must be `lolacore/{slug}`.
- Output files limited to scaffolder layout above.
- Prefer `dynamic:false` unless server render required.
- v1 excluded: `viewScriptModule`, Interactivity API, PHP-only autoRegister blocks, webpack/`@wordpress/scripts`, FSE template insertion.
- GPL-compatible generated code only.
- User-facing copy: block appears in inserter after install — not "plugin activated".

## SPEC_TEMPLATES

Static:
```json
{"title":"Notice Bar","slug":"notice-bar","attributes":{"message":{"type":"string","default":"Hello"}}}
```
→ `lolacore/notice-bar`, four core files, `save()` serializes attributes.

Dynamic:
```json
{"title":"Teaser","slug":"teaser","dynamic":true,"attributes":{"headline":{"type":"string","default":"News"}}}
```
→ adds `render.php`.

Inner:
```json
{"title":"Section","slug":"section","inner_blocks":true}
```
→ `InnerBlocks` in edit, `InnerBlocks.Content` in save.
