# Gutenberg Module for Drupal — Agent Instructions

## Project Overview

This is the **Drupal Gutenberg** module (`drupal/gutenberg`), a contrib module that brings the WordPress Gutenberg block editor to Drupal. It is hosted on drupal.org and supports Drupal 9.5, 10, and 11.

- **Repository:** https://git.drupalcode.org/project/gutenberg.git
- **Branch:** `3.0.x`
- **Gutenberg JS version:** 19.x (@wordpress/* packages)
- **PHP:** >=7.4
- **Node:** >=16

## Module Architecture

### PHP (src/)

~96 classes organized as:

| Directory | Purpose |
|---|---|
| `BlockProcessor/` | Block processing pipeline — each processor is a tagged service with a priority. The `GutenbergBlockProcessorManager` collects them via service collector. |
| `Controller/` | REST API controllers: `BlocksController`, `MediaController`, `ReusableBlocksController`, `EntityController`, `SearchController`, `OEmbedProxyUrlController`, `UtilsController` |
| `DataProvider/` | Entity data providers (media, file) collected via `EntityDataProviderManager` |
| `Plugin/` | Drupal plugins: `Filter/GutenbergFilter` (text filter), `Editor/Gutenberg` (editor plugin), `Field/FieldFormatter/GutenbergTextFormatter`, `GutenbergPlugin/` |
| `Service/` | `MediaService` — core media handling |
| `Parser/` | Block parser (port of WP block parser) |
| `Html/` | `TagProcessor` — HTML tag manipulation utility (port of WP) |
| `StyleEngine/` | CSS rule/declaration engine (auto-synced from upstream WP via `sync-style-engine.js`) |
| `BlockProcessor/block-supports/` | Auto-synced upstream PHP functions from `lib/block-supports/*.php` — **DO NOT EDIT** (managed by `sync-block-supports.js`) |
| `BlockProcessor/upstream/` | Stored baseline copies of full upstream files for offline diffing/reference — **DO NOT EDIT** |
| `BlockProcessor/block-supports-shims.php` | WP API shims bridging upstream PHP code to Drupal services (e.g., `WP_Block_Type_Registry` → `BlocksLibraryManager`) |
| `Persistence/` | Media type persistence |
| `Form/` | Block settings forms |
| `Render/` | Sidebar main content renderer |
| `Discovery/` | `BlockJsonDiscovery` — discovers block.json files |

### Block Processor Pipeline

Block processors run in priority order when rendering Gutenberg content. Defined in `gutenberg.services.yml`:

| Processor | Priority | Purpose |
|---|---|---|
| `DrupalBlockProcessor` | 50 | Renders Drupal blocks embedded in Gutenberg |
| `OEmbedProcessor` | 50 | Handles oEmbed blocks |
| `ReusableBlockProcessor` | 50 | Resolves reusable blocks |
| `ContentBlockProcessor` | 50 | Processes content blocks |
| `LayoutProcessor` | 50 | Layout support (gap, padding, alignment) — thin Drupal adapter delegating to synced upstream `gutenberg_render_layout_support_flag()` and `gutenberg_restore_group_inner_container()` |
| `DuotoneProcessor` | 50 | Duotone color filters |
| `DynamicRenderProcessor` | 0 | Dynamic block rendering |
| `FieldMappingProcessor` | -50 | Maps block attributes to Drupal fields |

### JavaScript / React (packages/)

Build tool: **@wordpress/scripts** (webpack-based, see `webpack.config.js`)

Source code is organized into **packages** under `packages/`. Each package has a `src/index.js` entry point and builds to `build/<package>/index.js` with an auto-generated `index.asset.php` for dependency tracking. CSS imported in JS is extracted to `build/<package>/style-index.css`.

#### Core Packages (exposed as `window.DrupalGutenberg.*`)

| Package | Description | Exports |
|---|---|---|
| `packages/components/` | React components + HOCs | DrupalIcon, DrupalBlock, DrupalMediaEntity, MediaBrowser, MediaLibrary, ContentBlock, BackButton, WelcomeGuide, Tabs, withGutenbergDialog, withNativeDialog |
| `packages/block-library/` | Custom Drupal blocks | registerDrupalBlocks, registerDrupalMedia, registerContentBlocks |
| `packages/filters/` | Editor filters + hooks | Side-effect only (addFilter calls) |
| `packages/plugins/` | Editor plugins (sidebars) | Side-effect only (registerPlugin calls) |
| `packages/overrides/` | Core block overrides | Side-effect only (replaces wp.blockLibrary) |
| `packages/edit-entity/` | Forked `edit-post` layout — owns the full editor chrome (header, sidebar, canvas). Passes `customSaveButton` to `@wordpress/editor`'s `<Editor>` to render Drupal form actions via the `DrupalFormActions` React component. | Layout, Header, MoreMenu, DrupalFormActions, WelcomeGuide, PreferencesModal, KeyboardShortcuts, etc. |
| `packages/editor/` | Main entry point — init, settings, i18n, api-fetch, url, data, drupal-store, gutenberg.js, editor-observers | Drupal.editors.gutenberg |

#### Standalone Entries (no global exposure)

| Entry | Description |
|---|---|
| `packages/admin/` | Admin config page behavior + CSS |
| `packages/sidebar/` | AJAX sidebar dialog + CSS |
| `packages/content-block-utils/` | Content block utilities + CSS |
| `packages/block-settings/` | Block settings form behavior |
| `packages/special-media-selection/` | Media selection storage (`Drupal.SpecialMediaSelection`) |
| `packages/claro/` | Claro theme CSS-only entry |

#### Other JS directories

| Directory | Purpose |
|---|---|
| `js/vendor/` | Third-party vendor libs (React, lodash, etc.) and `gutenberg/` subdirectory with @wordpress/* package bundles (built from npm via webpack) |
| `js/drupal-gutenberg-translations.js` | Generated translations file |
| `js/core-blocks-translations.js` | Translation scanner data |

#### Searching JS code

When searching for JavaScript/React code, **always search the readable source files in `packages/` first**, not the minified vendor bundles in `js/vendor/gutenberg/`. The `packages/` directory contains well-structured, commented source code that is much easier to read and navigate. Only fall back to searching `js/vendor/gutenberg/` when the code you need lives in upstream `@wordpress/*` packages and is not overridden in `packages/`.

#### Externalization Strategy

`@wordpress/dependency-extraction-webpack-plugin` is configured with:
- `@wordpress/*` → `window.wp.*` (standard WP externalization)
- `@drupal-gutenberg/*` → `window.DrupalGutenberg.*` (cross-package imports)

Cross-package imports (e.g., `import { DrupalBlock } from '@drupal-gutenberg/components'`) become webpack externals at build time. At runtime, Drupal's library system loads scripts in dependency order.

### CSS (css/)

SCSS source files live in `css/`. They are imported by their respective packages' JS entry points and extracted by webpack's `MiniCssExtractPlugin` to `build/<package>/style-index.css`.

### Key Configuration Files

| File | Purpose |
|---|---|
| `gutenberg.info.yml` | Module metadata and dependencies |
| `gutenberg.services.yml` | 34+ service definitions |
| `gutenberg.routing.yml` | 30+ REST API routes |
| `gutenberg.libraries.yml` | Library definitions — vendor libs, Gutenberg packages, module JS/CSS (auto-regenerated by `gutenberg-dependencies.php`) |
| `gutenberg.gutenberg.yml` | Default Gutenberg config (libraries-edit, libraries-view, dynamic-blocks) |
| `gutenberg.api.php` | Hook documentation |
| `gutenberg.module` | Main module file with hook implementations |
| `config/install/` | 19 install configuration files (content types, fields, views, filters) |
| `config/schema/` | Configuration schema definitions |

### Hooks (gutenberg.api.php)

Key hooks for extending the module:

- `hook_gutenberg_info_alter(&$info)` — Alter Gutenberg definitions (theme support, allowed blocks, etc.)
- `hook_gutenberg_render_block_alter(&$build, $block)` — Alter rendered block HTML
- `hook_gutenberg_render_block_BASE_BLOCK_ID_alter()` — Block-specific render alter
- `hook_gutenberg_block_view_alter(&$build, $block)` — Alter block render array
- `hook_gutenberg_media_search_query_alter()` — Alter media search queries
- `hook_gutenberg_media_library_view_alter()` — Alter media library UI
- `hook_gutenberg_node_type_route()` — Provide node type for custom routes

## Build System

There are **two separate build pipelines**: one for the module's own JS/CSS, and one for the upstream Gutenberg vendor JS (the `js/vendor/gutenberg/` packages). They use different tools and are run independently.

### Prerequisites

```bash
nvm use         # Switch to Node 16 (from .nvmrc)
npm install     # Install dependencies (also applies patches via patch-package)
```

### Quick Reference

```bash
npm run build              # Build module JS + SCSS + translations
npm run start              # Watch mode (JSX only, no SCSS)
npm run dev                # Build + watch
npm run build:vendor       # Rebuild Gutenberg vendor JS + third-party libs from npm
npm run build:vendor-dev   # Same, development mode (unminified)
npm run lint:js            # ESLint
npm run release            # Full release build (vendor + module JS)
npm run update-wp-packages -- 6.9            # Update @wordpress/* to a new WP version (dry-run: add --dry-run)
npm run sync-style-engine                     # Sync Style Engine PHP from upstream
npm run sync-block-supports                   # Sync block-supports PHP from upstream
```

### Pipeline 1: Module JS and CSS (`npm run build`)

This builds the module's own source files — packages, SCSS stylesheets, and translations. It does **not** touch the vendor Gutenberg packages.

**What it does** (uses `@wordpress/scripts` with custom `webpack.config.js`):

1. **Webpack build** (`wp-scripts build`): Bundles each package from `packages/<name>/src/index.js` into `build/<name>/index.js`. Core packages are exposed as `window.DrupalGutenberg.<camelCaseName>` globals. SCSS imported in JS is extracted to `build/<name>/style-index.css`. Each package also gets an auto-generated `build/<name>/index.asset.php` tracking its dependencies.

2. **Translations** via `drupal-gutenberg-translations`: Scans `packages/` for translatable strings, generates `js/drupal-gutenberg-translations.js`.

**Source → Output mapping:**

| Source | Output |
|---|---|
| `packages/<name>/src/index.js` | `build/<name>/index.js` + `build/<name>/index.asset.php` |
| SCSS imported by a package | `build/<name>/style-index.css` |
| `css/base/`, `css/components/` | (SCSS partials, imported by entry SCSS files) |

**Watch mode** (`npm run start`): Watches all package source files (JS, JSX, SCSS) via webpack and rebuilds on change.

**Important:** Never edit files in `build/` — they are auto-generated. Always edit source files in `packages/<name>/src/`.

### Pipeline 2: Gutenberg Vendor JS (`npm run build:vendor`)

This rebuilds the upstream WordPress Gutenberg packages (`js/vendor/gutenberg/`) and third-party vendor libraries (`js/vendor/*.min.js`). All packages are installed from **npm** and built with **webpack**.

**What it does** (three steps chained in the `build:vendor` script):

#### Step 1: Webpack build (`webpack.vendor.config.js`)

Builds each `@wordpress/*` package into a standalone bundle at `js/vendor/gutenberg/<package>/index.js`:

- **Entry points:** Auto-discovered from existing `js/vendor/gutenberg/` directories. Uses each package's **ESM entry** (`build-module/index.js`) for correct export handling — the CJS build has dynamic re-exports that webpack's tree-shaking strips out.
- **Library output:** Each bundle is exposed as `window.wp.<camelCaseName>` (e.g., `window.wp.blockEditor`). Packages like `api-fetch`, `deprecated`, `dom-ready`, `redux-routine`, `token-list`, `server-side-render`, `shortcode`, and `warning` export their default export.
- **Externals:** `@wordpress/dependency-extraction-webpack-plugin` externalizes all `@wordpress/*` imports to `window.wp.*` globals and generates `index.asset.php` dependency files. Note: some packages (`@wordpress/icons`, `@wordpress/interface`, `@wordpress/sync`, `@wordpress/undo-manager`) are in the plugin's `BUNDLED_PACKAGES` list — they are bundled inline, not externalized. These must be pinned to correct versions in `package.json`.
- **CSS:** `CopyWebpackPlugin` copies and minifies CSS from each package's `build-style/` directory to the output.
- **Third-party vendors:** Also copies pre-built UMD files for React, ReactDOM, lodash, moment, sprintf-js, and regenerator-runtime from `node_modules` to `js/vendor/`.
- **Patches:** `patch-package` applies patches from `patches/` on `npm install`. Currently patches `@wordpress/block-editor` to add a `<base href>` tag to the block editor preview iframe (`patches/@wordpress+block-editor+15.6.9.patch`).

#### Step 2: Block assets copy (`scripts/copy-block-assets.js`)

Copies per-block assets from the `@wordpress/block-library` npm package to `js/vendor/gutenberg/block-library/blocks/`:
- `block.json` files from `src/<block>/`
- Built JS files from `build/<block>/`
- Per-block CSS from `build-style/<block>/`

These are used by Drupal's `BlockJsonDiscovery` (`src/Discovery/BlockJsonDiscovery.php`).

#### Step 3: Library YAML regeneration (`scripts/gutenberg-dependencies.php`)

Reads each vendor package's `index.asset.php` for dependency info, scans for JS/CSS files, and regenerates `gutenberg.libraries.yml`. Whitelisted libraries (module's own like `admin`, `claro`, `blocks-edit`, `blocks-view`, etc.) are preserved; vendor entries are regenerated. Also scans `build/` for local package entries (`drupal-<name>`) and generates library entries with correct dependency mappings (`drupal-gutenberg-*` → `gutenberg/drupal-*`, `wp-*` → `gutenberg/*`).

**Flags:**

```bash
npm run build:vendor           # Production build
npm run build:vendor-dev       # Development build (unminified, with source maps)
```

**Full release** (`npm run release`): Runs `build:vendor` first, then `build` — rebuilds everything from scratch.

### Upgrading WordPress / Gutenberg Version

To upgrade all `@wordpress/*` packages to a new WP version:

```bash
npm run update-wp-packages -- 6.9 --dry-run   # Preview changes
npm run update-wp-packages -- 6.9              # Apply changes to package.json
npm install                                     # Install new versions + apply patches
npm run build:vendor                            # Rebuild all vendor packages
```

The script (`scripts/update-wp-packages.js`) resolves each `@wordpress/*` package's version from the `wp-<version>` npm dist-tag, updates `package.json`, and renames the patch file if `@wordpress/block-editor` changed version.

After updating packages, sync the upstream PHP ports:

```bash
npm run sync-style-engine                       # Sync Style Engine PHP
npm run sync-block-supports                     # Sync block-supports PHP
```

See [Syncing Upstream PHP Code](#syncing-upstream-php-code) for details and flags.

**Things to check after upgrading:**
- Verify the patch still applies cleanly (if not, manually fix and run `npx patch-package @wordpress/block-editor`)
- Check for new/removed `@wordpress/*` packages in the release notes
- Check if any packages need adding to `DEFAULT_EXPORT_PACKAGES` or `IGNORED_PACKAGES` in `webpack.vendor.config.js`
- Check if `BUNDLED_PACKAGES` in the dependency extraction plugin changed (run `cat node_modules/@wordpress/dependency-extraction-webpack-plugin/lib/util.js` — any packages listed there must be pinned in `package.json`)
- **Ensure the `drupal-editor` library in `gutenberg.libraries.yml` keeps the following as dependencies:** `media-attributes`, `format-library`, `commands`, `core-commands`, and `special-media-selection`. The library YAML regeneration script (`gutenberg-dependencies.php`) may drop or reorder these — verify they are present after regeneration and re-add them if missing.
- Review the `gutenberg.libraries.yml` diff
- Third-party vendors (React, lodash, etc.) are pinned separately — update manually in `package.json` if needed

### Syncing Upstream PHP Code

Several PHP subsystems are ported from WordPress/gutenberg PHP files. Rather than manually maintaining these ports, automated sync scripts fetch the upstream source, extract the relevant functions, and write them as auto-generated PHP files. The scripts resolve the correct upstream git commit from the installed `@wordpress/style-engine` npm package version.

**Never edit auto-generated files** — they will be overwritten on next sync. The generated files have a `DO NOT EDIT` header.

#### Style Engine Sync (`npm run sync-style-engine`)

Syncs the Style Engine PHP classes and utility functions from the `@wordpress/style-engine` npm package.

| Source (upstream) | Local output |
|---|---|
| `@wordpress/style-engine` npm package PHP files | `src/StyleEngine/*.php` |

Script: `scripts/sync-style-engine.js`

#### Block Supports Sync (`npm run sync-block-supports`)

Syncs block-supports PHP functions from `lib/block-supports/*.php` in the WordPress/gutenberg GitHub repository. Config-driven — adding a new processor sync is just adding an entry to `SYNC_CONFIG` in the script.

| Source (upstream) | Local output |
|---|---|
| `lib/block-supports/layout.php` (6 functions) | `src/BlockProcessor/block-supports/layout.php` |
| Full upstream file (for reference) | `src/BlockProcessor/upstream/layout.php` |

**Currently synced functions (layout):**
- `gutenberg_get_layout_definitions()` — layout type definitions (flow, flex, grid, constrained)
- `gutenberg_get_layout_style()` — generates CSS for a layout
- `gutenberg_incremental_id_per_prefix()` — stable sequential IDs per prefix
- `gutenberg_unique_id_from_values()` — deterministic hash-based IDs
- `gutenberg_render_layout_support_flag()` — main render function (layout classes + styles)
- `gutenberg_restore_group_inner_container()` — legacy group block inner container

Script: `scripts/sync-block-supports.js`

**Flags:**
```bash
npm run sync-block-supports                     # Fetch + sync all configured files
npm run sync-block-supports -- --dry-run        # Show diff only, don't write
npm run sync-block-supports -- --check-deps-only  # Validate dependency coverage
npm run sync-block-supports -- --show-render-diff  # Diff of functions for manual review
npm run sync-block-supports -- --offline        # Use stored baselines only (no fetch)
npm run sync-block-supports -- --ref v19.9.0    # Specific git ref
npm run sync-block-supports -- --only layout    # Sync only one file
```

#### WP API Shims (`block-supports-shims.php`)

The synced upstream functions expect WordPress globals and classes. The shims file (`src/BlockProcessor/block-supports-shims.php`) provides minimal stand-ins that delegate to Drupal services:

| WP API | Shim behavior |
|---|---|
| `WP_HTML_Tag_Processor` | Class alias to `Drupal\gutenberg\Html\TagProcessor` |
| `WP_Block_Type` | Minimal value object wrapping block definition arrays |
| `WP_Block_Type_Registry::get_instance()->get_registered()` | Delegates to `BlocksLibraryManager::getBlockDefinition()` |
| `block_has_support()` | Checks `$block_type->supports` array path |
| `sanitize_title()` | Simplified slug sanitizer for CSS class usage |
| `gutenberg_get_global_settings()` | Maps Drupal theme definitions to upstream format |
| `wp_should_skip_block_supports_serialization()` | Always returns `false` |
| `current_theme_supports()` | Returns `false` for `disable-layout-styles`, `true` otherwise |
| `wp_theme_has_theme_json()` | Always returns `true` (Drupal themes use theme.json-like definitions) |

Additional utility shims (`_wp_array_get`, `_wp_to_kebab_case`, `safecss_filter_attr`, `apply_filters`, etc.) are provided by `src/StyleEngine/utils.php`.

#### Loading Chain

`StyleEngine.php` orchestrates the require order via top-level `require_once` calls:

1. `src/StyleEngine/utils.php` — utility functions and WP shims
2. `src/StyleEngine/style-engine-gutenberg.php` — style engine API functions
3. `src/StyleEngine/class-wp-style-engine-*.php` — style engine classes
4. `src/BlockProcessor/block-supports-shims.php` — WP API shims for block-supports
5. `src/BlockProcessor/block-supports/*.php` — auto-synced block-supports functions (glob)

Block processors trigger this chain via `class_exists(StyleEngine::class)` which forces PSR-4 autoloading of `StyleEngine.php` and its side-effect requires.

#### Adding a New Block Support Sync

To sync a new upstream `lib/block-supports/*.php` file (e.g., colors):

1. Add a config entry in `SYNC_CONFIG` within `scripts/sync-block-supports.js`:
   ```js
   colors: {
     upstream: 'lib/block-supports/colors.php',
     extract: ['gutenberg_apply_colors_support', ...],
     diffOnly: ['gutenberg_render_colors_support'],
   },
   ```
2. Add any needed WP API shims to `block-supports-shims.php`
3. Run `npm run sync-block-supports` — creates `block-supports/colors.php` and `upstream/colors.php`
4. Refactor the corresponding `*Processor.php` to delegate to the synced functions
5. The `glob()` require in `StyleEngine.php` auto-discovers new files — no require edits needed

### Build Artifacts to Commit

When committing changes, include the generated output files:

| Changed source | Generated files to commit |
|---|---|
| Any `packages/` source file | The entire `build/` directory (rebuilt via `npm run build`) |
| Any `.scss` file | The `build/<package>/style-index.css` files |
| Gutenberg version upgrade (`build:vendor`) | All of `js/vendor/gutenberg/`, `js/vendor/*.min.js`, `gutenberg.libraries.yml`, `package.json`, `package-lock.json`, patches |
| Style Engine sync (`sync-style-engine`) | `src/StyleEngine/*.php` |
| Block supports sync (`sync-block-supports`) | `src/BlockProcessor/block-supports/*.php`, `src/BlockProcessor/upstream/*.php` |

### SCSS Architecture

SCSS follows a component-based structure with partials. Entry SCSS files are imported by their respective package JS entry points and extracted by webpack:

```
css/
├── base/
│   └── _variables.scss          # Shared variables
├── components/
│   ├── _block.scss              # Block styles
│   ├── _block-editor.scss       # Block editor overrides
│   ├── _drupal-block.scss       # Drupal block integration
│   ├── _edit-post.scss          # Edit post overrides
│   ├── _media-browser.scss      # Media browser dialog
│   ├── _media-entity.scss       # Media entity styles
│   ├── _media-entity-browser.scss
│   ├── _node-form.scss          # Node form overrides
│   ├── _sidebar.scss            # Sidebar styles
│   └── _ui-dialog.scss          # jQuery UI dialog overrides
├── admin.scss                   # → build/admin/style-index.css (imported by packages/admin/)
├── claro.scss                   # → build/claro/style-index.css (imported by packages/claro/)
├── content-block-form.scss      # → build/content-block-utils/style-index.css
├── editor.scss                  # → build/editor/style-index.css (imported by packages/editor/)
└── sidebar.scss                 # → build/sidebar/style-index.css (imported by packages/sidebar/)
```

Files prefixed with `_` are SCSS partials — they are `@import`ed by the entry SCSS files. The webpack `sass-loader` is configured with `includePaths: [css/]` so partials can be imported as `@import 'base/variables'`.

## Coding Conventions

### PHP
- Follow Drupal coding standards (https://www.drupal.org/docs/develop/standards)
- Services are defined in `gutenberg.services.yml`, not autowired
- Plugins use Drupal's annotation-based discovery
- Block processors are tagged services collected by `GutenbergBlockProcessorManager`

### JavaScript
- ESLint with Airbnb config + Prettier (single quotes, trailing commas, 80 char width)
- React components use JSX with ES module imports
- Gutenberg packages are imported via `@wordpress/*` (e.g., `import { registerBlockType } from '@wordpress/blocks'`)
- Cross-package imports use `@drupal-gutenberg/*` (e.g., `import { DrupalBlock } from '@drupal-gutenberg/components'`)
- Drupal globals (`Drupal`, `drupalSettings`, `jQuery`, `once`) are accessed directly with `/* global */` comments
- Drupal behaviors pattern used for non-React JS (`Drupal.behaviors`)
- Node version pinned to 16 (`.nvmrc`)

### CSS
- SCSS compiled to CSS
- Stylelint for linting
- BEM-style naming in component CSS

## Development Environment Setup

A recommended setup for working on this module:

1. **Drupal site**: Use [DDEV](https://ddev.readthedocs.io/) or a similar local environment to run a Drupal 10/11 site.
2. **Install the module**: Clone this repository into `web/modules/custom/gutenberg/` (or `web/modules/contrib/gutenberg/`) and enable it.
3. **Enable Gutenberg on a content type**: Go to Admin > Structure > Content types > [Type] > Edit, and select "Gutenberg" as the editor under "Submission form settings".
4. **Theme**: Install and enable the [Gutenberg Starter](https://www.drupal.org/project/gutenberg_starter) theme as a reference implementation. It demonstrates block variations, filters, styles, patterns, and full editor configuration via `<theme>.gutenberg.yml`.
5. **Node.js**: Run `nvm use` (Node 16) and `npm install` in the module root.

### Theme-side Gutenberg Configuration

Themes configure Gutenberg via `<theme>.gutenberg.yml`. Key settings:

```yaml
libraries-edit:
  - my_theme/editor            # JS/CSS loaded in the editor

theme-support:
  alignWide: true              # Wide/full-width block alignment
  supportsLayout: true         # Layout controls
  maxWidth: 900                # Editor content max width

experimental:
  layout:
    contentSize: 670px         # Default content width
    wideSize: 954px            # Wide content width
  typography:
    fontSizes: [...]           # Predefined font size scale
  color:
    palette: [...]             # Theme color palette
  spacing:
    spacingSizes: [...]        # Predefined spacing scale
```

### Theme JS Extension Points

Themes extend the editor via JS libraries attached in `libraries-edit`:

- **Block Variations** — Register named variations of existing blocks via `wp.blocks.registerBlockVariation()`
- **Block Filters** — Modify block edit/save behavior with `wp.hooks.addFilter()`
- **Block Styles** — Register visual style alternatives via `wp.blocks.registerBlockStyle()`
- **Block Patterns** — Defined in `<theme>.gutenberg.yml` as pre-built block compositions

## Common Development Tasks

### Adding a new block processor
1. Create a class in `src/BlockProcessor/` implementing `GutenbergBlockProcessorInterface`
2. Register it as a tagged service in `gutenberg.services.yml` with tag `gutenberg_block_processor` and a `priority`

### Adding a new REST endpoint
1. Add a controller method in `src/Controller/`
2. Define the route in `gutenberg.routing.yml`

### Adding a new JS block
1. Create a directory under `packages/block-library/src/blocks/<block-name>/`
2. Register the block in `packages/block-library/src/index.js`
3. Run `npm run build` to rebuild

### Syncing upstream PHP code after a version upgrade
1. Run `npm run sync-style-engine` to sync the Style Engine PHP classes
2. Run `npm run sync-block-supports` to sync the block-supports PHP functions
3. Review the output — the scripts report dependency coverage and any changes
4. Use `npm run sync-block-supports -- --show-render-diff` to see upstream changes in functions not yet synced (for manual porting)
5. Test blocks with layout support (Group, Columns, Cover, etc.) to verify CSS classes and styles

### Rebuilding JS after changes
```bash
npm run build          # One-time build (module JS/CSS + translations)
npm run start          # Watch mode (JS/JSX/SCSS via webpack)
npm run build:vendor   # Rebuild vendor packages (after npm install or version upgrade)
npm run release        # Full release build (vendor + module JS)
```
