fix: Task 6 review round 1 - admin label overlap, heading default level, structure deny-list, row widths (S41)

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 15:26:12 +02:00
co-authored by Claude Opus 5.5
parent 22bb1528db
commit 909ac82594
7 changed files with 294 additions and 11 deletions
@@ -7,7 +7,9 @@ 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 WP_Error;
/**
@@ -20,6 +22,8 @@ use WP_Error;
*/
final class DiviUpdateAbility implements Ability
{
private const SIZING = 'module.decoration.sizing';
public function __construct(private DiviWriter $writer)
{
}
@@ -117,7 +121,7 @@ final class DiviUpdateAbility implements Ability
$invalidPaths = false;
/** @var array<string, int> $seen id => position of its first change */
$seen = [];
/** @var list<array{0: Block, 1: array<string, mixed>}> $updates */
/** @var list<array{0: Block, 1: array<string, mixed>, 2: string}> $updates */
$updates = [];
foreach ($changes as $position => $change) {
$field = sprintf('changes[%d]', $position);
@@ -150,7 +154,11 @@ final class DiviUpdateAbility implements Ability
$result = $composer->update($block, $change, $field);
$errors += $result['errors'];
$invalidPaths = $invalidPaths || $result['invalid_paths'] !== [];
$updates[] = [$block, $result['attrs']];
$updates[] = [$block, $result['attrs'], $field];
}
if ($errors === []) {
$errors = $this->rowWidths($page, $composer, $updates);
}
if ($errors !== []) {
@@ -172,4 +180,55 @@ final class DiviUpdateAbility implements Ability
return $this->writer->save($this->name(), $page, $change, $input);
}
/**
* Ruling S41: a column whose desktop width changes must leave its row filled exactly – the
* rules of "columns" (Composer::rowWidthsError()), checked over the row after all changes.
*
* @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
{
$new = [];
$rows = [];
foreach ($updates as [$block, $attrs, $field]) {
if ($block->name() !== 'divi/column' || self::width($attrs) === self::width($block->attrs())) {
continue;
}
$new[spl_object_id($block)] = $attrs;
$row = $page->index->parentOf($block);
if ($row instanceof Block) {
$rows[spl_object_id($row)]['row'] = $row;
$rows[spl_object_id($row)]['fields'][] = $field;
}
}
$errors = [];
foreach ($rows as ['row' => $row, 'fields' => $fields]) {
$widths = [];
foreach ($row->children() as $column) {
$widths[] = self::width($new[spl_object_id($column)] ?? $column->attrs());
}
$error = $composer->rowWidthsError($widths);
if ($error !== null) {
foreach ($fields as $field) {
$errors[$field . '.attrs.' . self::SIZING] = $error
. ' (Breiten aller Spalten der Zeile zusammen ändern)';
}
}
}
return $errors;
}
/** @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;
}
}