fix: builder column classes only – no 4_5, all breakpoints checked (S49)

New rows no longer accept 4_5 (the builder does not offer it). On update,
the flexType of every non-desktop breakpoint (tablet, phone, …) is
format-checked against the builder list like desktop. Guide updated.

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:34:45 +02:00
co-authored by Claude Opus 5.5
parent e04a5304f7
commit eda592548e
4 changed files with 103 additions and 18 deletions
+58 -15
View File
@@ -10,13 +10,16 @@ use LIT\MCP\Integrations\Divi\BlockTree\NodeIndex;
/**
* Column widths of Divi rows (pure, static): the "columns" input of new rows
* ("1_3,2_3" → flexTypes, ruling S34) and the desktop flexType checks on update (rulings S41,
* S43). Every width this class returns is built from digits or taken from a fixed list.
* ("1_3,2_3" → flexTypes, ruling S34) and the flexType checks on update (rulings S41, S43,
* S49). Every width this class returns is built from digits or taken from a fixed list.
*/
final class ColumnWidths
{
/** Column fractions Divi's flex layout knows besides multiples of 1/24. */
private const SPECIAL_WIDTHS = ['1_5', '2_5', '3_5', '4_5', '1_7', '1_9', '1_10', '1_11'];
/**
* Column fractions of "columns" besides multiples of 1/24: the builder's special column
* classes (ruling S49 – no 4_5, the builder does not offer it).
*/
private const SPECIAL_WIDTHS = ['1_5', '2_5', '3_5', '1_7', '1_9', '1_10', '1_11'];
/**
* The builder's "Column Class" choices (Divi 5.2.1 visual-builder/build/field-library.js):
@@ -29,6 +32,8 @@ final class ColumnWidths
/** Divi's column default (column/module-default-render-attributes.json). */
private const DEFAULT_WIDTH = '24_24';
private const CLASS_LIST = '1_24 … 24_24, 1_5, 2_5, 3_5, 1_7, 1_9, 1_10, 1_11 oder none';
private const FORMAT = 'Spaltenaufteilung wie "1_2,1_2" oder "1_3,2_3" erwartet';
private const FULL_ROW = 'die Spaltenbreiten müssen zusammen eine volle Zeile ergeben (z. B. "1_3,2_3")';
@@ -100,12 +105,13 @@ final class ColumnWidths
}
/**
* Column widths on update (rulings S41, S43). Every new desktop flexType must be one of the
* builder's column classes (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 (rowError()), 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.
* Column widths on update (rulings S41, S43, S49). Every new flexType – desktop and every
* other breakpoint – must be one of the builder's column classes (isColumnClass()) or null
* (removed; desktop then is Divi's default 24_24). The desktop row sum is only checked for
* rows that are exact today – every column has a class other than "none", the classes fill
* the row (rowError()), 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 Block, its new attrs, input path.
* @return array<string, string>
@@ -116,14 +122,27 @@ final class ColumnWidths
$new = [];
$rows = [];
foreach ($updates as [$block, $attrs, $field]) {
if ($block->name() !== 'divi/column') {
continue;
}
$breakpoint = self::invalidBreakpoint($block->attrs(), $attrs);
if ($breakpoint !== null) {
$errors[$field . '.attrs.' . self::SIZING] = sprintf(
'Spaltenbreite (flexType) für %s wie im Divi-Builder erwartet: %s',
$breakpoint,
self::CLASS_LIST
);
}
$width = self::width($attrs);
if ($block->name() !== 'divi/column' || $width === self::width($block->attrs())) {
if ($width === self::width($block->attrs())) {
continue;
}
if ($width !== null && ! self::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';
. self::CLASS_LIST;
continue;
}
@@ -172,10 +191,34 @@ final class ColumnWidths
return self::rowError($widths) === null;
}
/** @param array<string, mixed> $attrs */
private static function width(array $attrs): mixed
/**
* The first non-desktop breakpoint whose flexType changed to something that is no column
* class (ruling S49), or null.
*
* @param array<string, mixed> $before
* @param array<string, mixed> $after
*/
private static function invalidBreakpoint(array $before, array $after): ?string
{
$sizing = AttrMap::desktop($attrs, self::SIZING);
foreach (AttrMap::BREAKPOINTS as $breakpoint) {
if ($breakpoint === 'desktop') {
continue;
}
$width = self::width($after, $breakpoint);
if ($width !== null && $width !== self::width($before, $breakpoint) && ! self::isColumnClass($width)) {
return $breakpoint;
}
}
return null;
}
/** @param array<string, mixed> $attrs */
private static function width(array $attrs, string $breakpoint = 'desktop'): mixed
{
$map = AttrMap::get($attrs, self::SIZING);
$sizing = is_array($map) && is_array($map[$breakpoint] ?? null) ? ($map[$breakpoint]['value'] ?? null) : null;
return is_array($sizing) ? ($sizing['flexType'] ?? null) : null;
}