fix: Task 6 review round 2 - builder column classes, sum check only for exact rows, unknown path wins, column structure text (S43)

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:31:05 +02:00
co-authored by Claude Opus 5.5
parent 68ca4f45ec
commit 0fddece18d
6 changed files with 153 additions and 10 deletions
@@ -24,6 +24,9 @@ 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)
{
}
@@ -182,22 +185,34 @@ final class DiviUpdateAbility implements Ability
}
/**
* 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.
* 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]) {
if ($block->name() !== 'divi/column' || self::width($attrs) === self::width($block->attrs())) {
$width = self::width($attrs);
if ($block->name() !== 'divi/column' || $width === self::width($block->attrs())) {
continue;
}
$new[spl_object_id($block)] = $attrs;
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;
@@ -205,14 +220,19 @@ final class DiviUpdateAbility implements Ability
}
}
$errors = [];
foreach ($rows as ['row' => $row, 'fields' => $fields]) {
$widths = [];
$before = [];
$after = [];
foreach ($row->children() as $column) {
$widths[] = self::width($new[spl_object_id($column)] ?? $column->attrs());
$before[] = self::width($column->attrs());
$after[] = $new[spl_object_id($column)] ?? self::width($column->attrs());
}
$error = $composer->rowWidthsError($widths);
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
@@ -224,6 +244,19 @@ final class DiviUpdateAbility implements Ability
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
{