refactor: ColumnWidths holds the column-width rules (H5)

Composer::row uses ColumnWidths::parse; rowWidthsError and its error-state
save/restore are gone. The update width check moves out of
DiviUpdateAbility into ColumnWidths::updateErrors. Behaviour unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
2026-09-26 18:21:03 +02:00
co-authored by Claude Opus 5.5
parent 6cf0a565e6
commit 9da236b0e3
5 changed files with 267 additions and 219 deletions
@@ -7,9 +7,8 @@ namespace LIT\MCP\Integrations\Divi\Pages;
use LIT\MCP\Abilities\Ability;
use LIT\MCP\Abilities\Errors;
use LIT\MCP\Abilities\ToolResult;
use LIT\MCP\Integrations\Divi\BlockTree\AttrMap;
use LIT\MCP\Integrations\Divi\BlockTree\Block;
use LIT\MCP\Integrations\Divi\Compose\Composer;
use LIT\MCP\Integrations\Divi\Compose\ColumnWidths;
use WP_Error;
/**
@@ -22,11 +21,6 @@ use WP_Error;
*/
final class DiviUpdateAbility implements Ability
{
private const SIZING = 'module.decoration.sizing';
/** Divi's column default (column/module-default-render-attributes.json). */
private const DEFAULT_WIDTH = '24_24';
public function __construct(private DiviWriter $writer)
{
}
@@ -161,7 +155,7 @@ final class DiviUpdateAbility implements Ability
}
if ($errors === []) {
$errors = $this->rowWidths($page, $composer, $updates);
$errors = ColumnWidths::updateErrors($page->index, $updates);
}
if ($errors !== []) {
@@ -183,85 +177,4 @@ final class DiviUpdateAbility implements Ability
return $this->writer->save($this->name(), $page, $change, $input);
}
/**
* Column widths on update (rulings S41, S43). Every new desktop flexType must be one of the
* builder's column classes (Composer::isColumnClass()) or null (removed: Divi's default
* 24_24). The row sum is only checked for rows that are exact today – every column has a
* class other than "none", the classes fill the row (Composer::rowWidthsError()), the row
* does not wrap or use a grid on desktop – and when no new width is "none". Such a row
* must stay exact after all changes; other rows are laid out by Divi as the builder allows.
*
* @param list<array{0: Block, 1: array<string, mixed>, 2: string}> $updates
* @return array<string, string>
*/
private function rowWidths(DiviPage $page, Composer $composer, array $updates): array
{
$errors = [];
$new = [];
$rows = [];
foreach ($updates as [$block, $attrs, $field]) {
$width = self::width($attrs);
if ($block->name() !== 'divi/column' || $width === self::width($block->attrs())) {
continue;
}
if ($width !== null && ! Composer::isColumnClass($width)) {
$errors[$field . '.attrs.' . self::SIZING] = 'Spaltenbreite (flexType) wie im Divi-Builder erwartet: '
. '1_24 … 24_24, 1_5, 2_5, 3_5, 1_7, 1_9, 1_10, 1_11 oder none';
continue;
}
$new[spl_object_id($block)] = $width ?? self::DEFAULT_WIDTH;
$row = $page->index->parentOf($block);
if ($row instanceof Block) {
$rows[spl_object_id($row)]['row'] = $row;
$rows[spl_object_id($row)]['fields'][] = $field;
}
}
foreach ($rows as ['row' => $row, 'fields' => $fields]) {
$before = [];
$after = [];
foreach ($row->children() as $column) {
$before[] = self::width($column->attrs());
$after[] = $new[spl_object_id($column)] ?? self::width($column->attrs());
}
if (! self::isExact($row, $before, $composer) || in_array('none', $after, true)) {
continue;
}
$error = $composer->rowWidthsError($after);
if ($error !== null) {
foreach ($fields as $field) {
$errors[$field . '.attrs.' . self::SIZING] = $error
. ' (Breiten aller Spalten der Zeile zusammen ändern)';
}
}
}
return $errors;
}
/** @param list<mixed> $widths Desktop flexTypes of the row's columns today. */
private static function isExact(Block $row, array $widths, Composer $composer): bool
{
$layout = AttrMap::desktop($row->attrs(), 'module.decoration.layout');
$wraps = is_array($layout)
&& (($layout['flexWrap'] ?? null) === 'wrap' || ($layout['display'] ?? null) === 'grid');
if ($wraps) {
return false;
}
return $composer->rowWidthsError($widths) === null;
}
/** @param array<string, mixed> $attrs */
private static function width(array $attrs): mixed
{
$sizing = AttrMap::desktop($attrs, self::SIZING);
return is_array($sizing) ? ($sizing['flexType'] ?? null) : null;
}
}