feat: Divi composer – short/full form to blocks with structure rules, presets and labels

Labels, background_color and preset ids go through the choke point (S13);
ambiguous preset names fail with their ids (S13a); fullwidth + rows is an
error. Carry-ins: control characters stripped before the marker check (S32),
kind override may only tighten, reference alt texts dropped, color check via
VariableRef::typeOf (S33).

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 14:15:33 +02:00
co-authored by Claude Opus 5.5
parent f86cd5b34a
commit 4b72c2bb42
11 changed files with 1314 additions and 17 deletions
@@ -25,9 +25,6 @@ final class ShortformWriter
private const COLOR_ERROR = 'Farbe erwartet, z. B. {{color:primary}} oder #1a1a1a';
/** Prefix of every color reference VariableRef::serialize() writes (fixed key order). */
private const COLOR_REFERENCE = '$variable({"type":"color",';
/** Curated kind => sanitizer kind of AttrWriter::cleanValue(). */
private const CLEAN_KIND = ['html' => 'html', 'code' => 'code', 'url' => 'url', 'text' => 'text'];
@@ -89,7 +86,7 @@ final class ShortformWriter
$value = trim($this->placeholders->resolve($value));
if (VariableRef::contains($value)) {
$reference = $this->attrs->cleanValue($module, $path, $value, $subKey, 'text');
if (! is_string($reference) || ! str_starts_with($reference, self::COLOR_REFERENCE)) {
if (! is_string($reference) || VariableRef::typeOf($reference) !== 'color') {
throw new InvalidArgumentException(self::COLOR_ERROR);
}
@@ -166,14 +163,33 @@ final class ShortformWriter
$attrs = AttrMap::set($attrs, $base . $idKey, (string) $id);
}
if ($altKey !== null && $withAlt && $image['alt'] !== '') {
$alt = $this->attrs->cleanValue($module, $path, $image['alt'], $altKey, 'text');
$attrs = AttrMap::set($attrs, $base . $altKey, $alt);
$alt = $altKey !== null && $withAlt ? $this->alt($module, $path, $image['alt'], $altKey) : '';
if ($alt !== '') {
$attrs = AttrMap::set($attrs, $base . (string) $altKey, $alt);
}
return $attrs;
}
/**
* The attachment's alt text, cleaned as text – or '' (no alt) when it carries a reference,
* whole or rebuilt by cleaning (ruling S33b): library data never becomes dynamic content.
*/
private function alt(string $module, string $path, string $alt, string $altKey): string
{
if ($alt === '' || VariableRef::contains($alt)) {
return '';
}
try {
$clean = $this->attrs->cleanValue($module, $path, $alt, $altKey, 'text');
} catch (InvalidArgumentException) {
return '';
}
return is_string($clean) ? $clean : '';
}
/**
* @param array<string, mixed> $spec
* @throws InvalidArgumentException